diff --git a/common/src/app/common/files/libraries_helpers.cljc b/common/src/app/common/files/libraries_helpers.cljc index d700237d2..5aa612a52 100644 --- a/common/src/app/common/files/libraries_helpers.cljc +++ b/common/src/app/common/files/libraries_helpers.cljc @@ -2098,3 +2098,189 @@ (cond-> changes (some? swap-slot) (generate-sync-head file-full libraries container id components-v2 true)))) + +(defn generate-relocate-shapes [changes objects parents parent-id page-id to-index ids] + (let [groups-to-delete + (loop [current-id (first parents) + to-check (rest parents) + removed-id? (set ids) + result #{}] + + (if-not current-id + ;; Base case, no next element + result + + (let [group (get objects current-id)] + (if (and (not= :frame (:type group)) + (not= current-id parent-id) + (empty? (remove removed-id? (:shapes group)))) + + ;; Adds group to the remove and check its parent + (let [to-check (concat to-check [(cfh/get-parent-id objects current-id)])] + (recur (first to-check) + (rest to-check) + (conj removed-id? current-id) + (conj result current-id))) + + ;; otherwise recur + (recur (first to-check) + (rest to-check) + removed-id? + result))))) + + groups-to-unmask + (reduce (fn [group-ids id] + ;; When a masked group loses its mask shape, because it's + ;; moved outside the group, the mask condition must be + ;; removed, and it must be converted to a normal group. + (let [obj (get objects id) + parent (get objects (:parent-id obj))] + (if (and (:masked-group parent) + (= id (first (:shapes parent))) + (not= (:id parent) parent-id)) + (conj group-ids (:id parent)) + group-ids))) + #{} + ids) + + + ;; TODO: Probably implementing this using loop/recur will + ;; be more efficient than using reduce and continuous data + ;; desturcturing. + + ;; Sets the correct components metadata for the moved shapes + ;; `shapes-to-detach` Detach from a component instance a shape that was inside a component and is moved outside + ;; `shapes-to-deroot` Removes the root flag from a component instance moved inside another component + ;; `shapes-to-reroot` Adds a root flag when a nested component instance is moved outside + [shapes-to-detach shapes-to-deroot shapes-to-reroot] + (reduce (fn [[shapes-to-detach shapes-to-deroot shapes-to-reroot] id] + (let [shape (get objects id) + parent (get objects parent-id) + component-shape (ctn/get-component-shape objects shape) + component-shape-parent (ctn/get-component-shape objects parent {:allow-main? true}) + root-parent (ctn/get-instance-root objects parent) + + detach? (and (ctk/in-component-copy-not-head? shape) + (not= (:id component-shape) + (:id component-shape-parent))) + deroot? (and (ctk/instance-root? shape) + root-parent) + reroot? (and (ctk/subinstance-head? shape) + (not component-shape-parent)) + + ids-to-detach (when detach? + (cons id (cfh/get-children-ids objects id)))] + + [(cond-> shapes-to-detach detach? (into ids-to-detach)) + (cond-> shapes-to-deroot deroot? (conj id)) + (cond-> shapes-to-reroot reroot? (conj id))])) + [[] [] []] + (->> ids + (mapcat #(ctn/get-child-heads objects %)) + (map :id))) + + shapes-to-unconstraint ids + + ordered-indexes (cfh/order-by-indexed-shapes objects ids) + shapes (map (d/getf objects) ordered-indexes) + parent (get objects parent-id) + component-main-parent (ctn/find-component-main objects parent false) + child-heads + (->> ordered-indexes + (mapcat #(ctn/get-child-heads objects %)) + (map :id))] + + (-> 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 ordered-indexes ctl/remove-layout-item-data)) + + ;; Remove the hide in viewer flag + (cond-> (and (not= uuid/zero parent-id) (cfh/frame-shape? parent)) + (pcb/update-shapes ordered-indexes #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true)))) + + ;; Remove the swap slots if it is moving to a different component + (pcb/update-shapes child-heads + (fn [shape] + (cond-> shape + (not= component-main-parent (ctn/find-component-main objects shape false)) + (ctk/remove-swap-slot)))) + + ;; 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))) + + ;; Move the shapes + (pcb/change-parent parent-id + shapes + to-index) + + ;; Remove empty groups + (pcb/remove-objects groups-to-delete) + + ;; Unmask groups whose mask have moved outside + (pcb/update-shapes groups-to-unmask + (fn [shape] + (assoc shape :masked-group false))) + + ;; Detach shapes moved out of their component + (pcb/update-shapes shapes-to-detach ctk/detach-shape) + + ;; Make non root a component moved inside another one + (pcb/update-shapes shapes-to-deroot + (fn [shape] + (assoc shape :component-root nil))) + + ;; Make root a subcomponent moved outside its parent component + (pcb/update-shapes shapes-to-reroot + (fn [shape] + (assoc shape :component-root true))) + + ;; Reset constraints depending on the new parent + (pcb/update-shapes shapes-to-unconstraint + (fn [shape] + (let [frame-id (if (= (:type parent) :frame) + (:id parent) + (:frame-id parent)) + moved-shape (assoc shape + :parent-id parent-id + :frame-id frame-id)] + (assoc shape + :constraints-h (gsh/default-constraints-h moved-shape) + :constraints-v (gsh/default-constraints-v moved-shape)))) + {:ignore-touched true}) + + ;; Fix the sizing when moving a shape + (pcb/update-shapes parents + (fn [parent] + (if (ctl/flex-layout? parent) + (cond-> parent + (ctl/change-h-sizing? (:id parent) objects (:shapes parent)) + (assoc :layout-item-h-sizing :fix) + + (ctl/change-v-sizing? (:id parent) objects (:shapes parent)) + (assoc :layout-item-v-sizing :fix)) + parent))) + + ;; Update grid layout + (cond-> (ctl/grid-layout? objects parent-id) + (pcb/update-shapes [parent-id] #(ctl/add-children-to-index % ids objects to-index))) + + (pcb/update-shapes parents + (fn [parent objects] + (cond-> parent + (ctl/grid-layout? parent) + (ctl/assign-cells objects))) + {:with-objects? true}) + + (pcb/reorder-grid-children parents) + + ;; If parent locked, lock the added shapes + (cond-> (:blocked parent) + (pcb/update-shapes ordered-indexes #(assoc % :blocked true))) + + ;; Resize parent containers that need to + (pcb/resize-parents parents)))) diff --git a/common/test/common_tests/helpers/compositions.cljc b/common/test/common_tests/helpers/compositions.cljc index 6306fc51a..e28526ee6 100644 --- a/common/test/common_tests/helpers/compositions.cljc +++ b/common/test/common_tests/helpers/compositions.cljc @@ -6,7 +6,8 @@ (ns common-tests.helpers.compositions (:require - [common-tests.helpers.files :as thf])) + [common-tests.helpers.files :as thf] + [common-tests.helpers.ids-map :as thi])) (defn add-rect [file rect-label] @@ -15,10 +16,11 @@ :name "Rect1")) (defn add-frame - [file frame-label] - (thf/add-sample-shape file frame-label - :type :frame - :name "Frame1")) + ([file frame-label & {:keys [parent-label]}] + (thf/add-sample-shape file frame-label + :type :frame + :name "Frame1" + :parent-label parent-label))) (defn add-frame-with-child [file frame-label child-label] diff --git a/common/test/common_tests/helpers/debug.cljc b/common/test/common_tests/helpers/debug.cljc new file mode 100644 index 000000000..984e47159 --- /dev/null +++ b/common/test/common_tests/helpers/debug.cljc @@ -0,0 +1,33 @@ +(ns common-tests.helpers.debug + (:require + [app.common.uuid :as uuid] + [common-tests.helpers.ids-map :as thi])) + +(defn dump-shape + "Dumps a shape, with each attribute in a line" + [shape] + (println "{") + (doseq [[k v] (sort shape)] + (when (some? v) + (println (str " " k " : " v)))) + (println "}")) + +(defn- stringify-keys [m keys] + (apply str (interpose ", " (map #(str % ": " (get m %)) keys)))) + +(defn dump-page + "Dumps the layer tree of the page. Prints the label of each shape, and the specified keys. + Example: (thd/dump-page (thf/current-page file) [:id :touched])" + ([page keys] + (dump-page page uuid/zero "" keys)) + ([page id padding keys] + (let [objects (vals (:objects page)) + root-objects (filter #(and + (= (:parent-id %) id) + (not= (:id %) id)) + objects)] + (doseq [val root-objects] + (println padding (thi/label (:id val)) + (when keys + (str "[" (stringify-keys val keys) "]"))) + (dump-page page (:id val) (str padding " ") keys))))) diff --git a/common/test/common_tests/helpers/files.cljc b/common/test/common_tests/helpers/files.cljc index 306d2ef81..bf605f5dc 100644 --- a/common/test/common_tests/helpers/files.cljc +++ b/common/test/common_tests/helpers/files.cljc @@ -9,12 +9,15 @@ [app.common.data.macros :as dm] [app.common.features :as ffeat] [app.common.files.changes :as cfc] + [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] + [app.common.files.libraries-helpers :as cflh] [app.common.files.validate :as cfv] [app.common.geom.point :as gpt] [app.common.pprint :refer [pprint]] [app.common.types.color :as ctc] [app.common.types.colors-list :as ctcl] + [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] @@ -206,8 +209,16 @@ [file id] (ctkl/get-component (:data file) id)) +(defn set-child-label + [file shape-label child-idx label] + (let [id (-> (get-shape file shape-label) + :shapes + (nth child-idx))] + (when id + (thi/set-id! label id)))) + (defn instantiate-component - [file component-label copy-root-label & {:keys [parent-label library] :as params}] + [file component-label copy-root-label & {:keys [parent-label library children-labels] :as params}] (let [page (current-page file) library (or library file) component (get-component library component-label) @@ -236,33 +247,60 @@ (assoc :frame-id frame-id) (and (some? parent) (ctn/in-any-component? (:objects page) parent)) - (dissoc :component-root))] + (dissoc :component-root)) + file' (ctf/update-file-data + file + (fn [file-data] + (as-> file-data $ + (ctpl/update-page $ + (:id page) + #(ctst/add-shape (:id copy-root') + copy-root' + % + frame-id + parent-id + nil + true)) + (reduce (fn [file-data shape] + (ctpl/update-page file-data + (:id page) + #(ctst/add-shape (:id shape) + shape + % + (:parent-id shape) + (:frame-id shape) + nil + true))) + $ + (remove #(= (:id %) (:did copy-root')) copy-shapes)))))] + (when children-labels + (dotimes [idx (count children-labels)] + (set-child-label file' copy-root-label idx (nth children-labels idx)))) + file')) + + + +(defn component-swap + [file shape-label new-component-label new-shape-label & {:keys [library] :as params}] + (let [shape (get-shape file shape-label) + library (or library file) + libraries {(:id library) library} + page (current-page file) + objects (:objects page) + id-new-component (-> (get-component library new-component-label) + :id) + + ;; Store the properties that need to be maintained when the component is swapped + keep-props-values (select-keys shape ctk/swap-keep-attrs) + + + [new_shape _ changes] + (-> (pcb/empty-changes nil (:id page)) + (cflh/generate-component-swap objects shape (:data file) page libraries id-new-component 0 nil keep-props-values))] + + (thi/set-id! new-shape-label (:id new_shape)) + (apply-changes file changes))) - (ctf/update-file-data - file - (fn [file-data] - (as-> file-data $ - (ctpl/update-page $ - (:id page) - #(ctst/add-shape (:id copy-root') - copy-root' - % - frame-id - parent-id - nil - true)) - (reduce (fn [file-data shape] - (ctpl/update-page file-data - (:id page) - #(ctst/add-shape (:id shape) - shape - % - (:parent-id shape) - (:frame-id shape) - nil - true))) - $ - (remove #(= (:id %) (:did copy-root')) copy-shapes))))))) (defn sample-color [label & {:keys [] :as params}] diff --git a/common/test/common_tests/helpers/ids_map.cljc b/common/test/common_tests/helpers/ids_map.cljc index dc196598d..cec7242cf 100644 --- a/common/test/common_tests/helpers/ids_map.cljc +++ b/common/test/common_tests/helpers/ids_map.cljc @@ -34,3 +34,9 @@ [f] (reset-idmap!) (f)) + +(defn label [id] + (->> @idmap + (filter #(= id (val %))) + (map key) + (first))) diff --git a/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc b/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc new file mode 100644 index 000000000..5773b1df0 --- /dev/null +++ b/common/test/common_tests/logic/comp_remove_swap_slots_test.cljc @@ -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.comp-remove-swap-slots-test + (:require + [app.common.files.changes-builder :as pcb] + [app.common.files.libraries-helpers :as cflh] + [app.common.types.component :as ctk] + [app.common.uuid :as uuid] + [clojure.test :as t] + [common-tests.helpers.compositions :as tho] + [common-tests.helpers.files :as thf] + [common-tests.helpers.ids-map :as thi])) + +(t/use-fixtures :each thi/test-fixture) + + +;; Related .penpot file: common/test/cases/remove-swap-slots.penpot +(defn- setup-file + [] + ;; :frame-b1 [:id: 3aee2370-44e4-81c8-8004-46e56a459d70, :touched: ] + ;; :blue1 [:id: 3aee2370-44e4-81c8-8004-46e56a45fc55, :touched: #{:swap-slot-3aee2370-44e4-81c8-8004-46e56a459d75}] + ;; :green-copy [:id: 3aee2370-44e4-81c8-8004-46e56a45fc56, :touched: ] + ;; :blue-copy-in-green-copy [:id: 3aee2370-44e4-81c8-8004-46e56a4631a4, :touched: #{:swap-slot-3aee2370-44e4-81c8-8004-46e56a459d6f}] + ;; :frame-yellow [:id: 3aee2370-44e4-81c8-8004-46e56a459d73, :touched: ] + ;; :frame-green [:id: 3aee2370-44e4-81c8-8004-46e56a459d6c, :touched: ] + ;; :red-copy-green [:id: 3aee2370-44e4-81c8-8004-46e56a459d6f, :touched: ] + ;; :frame-blue [:id: 3aee2370-44e4-81c8-8004-46e56a459d69, :touched: ] + ;; :frame-b2 [:id: 3aee2370-44e4-81c8-8004-46e56a4631a5, :touched: ] + ;; :frame-red [:id: 3aee2370-44e4-81c8-8004-46e56a459d66, :touched: ] + + (-> (thf/sample-file :file1) + (tho/add-frame :frame-red) + (thf/make-component :red :frame-red) + (tho/add-frame :frame-blue) + (thf/make-component :blue :frame-blue) + (tho/add-frame :frame-green) + (thf/make-component :green :frame-green) + (thf/instantiate-component :red :red-copy-green :parent-label :frame-green) + (tho/add-frame :frame-b1) + (thf/make-component :b1 :frame-b1) + (tho/add-frame :frame-yellow :parent-label :frame-b1) + (thf/instantiate-component :red :red-copy :parent-label :frame-b1) + (thf/component-swap :red-copy :blue :blue1) + (thf/instantiate-component :green :green-copy :parent-label :frame-b1 :children-labels [:red-copy-in-green-copy]) + (thf/component-swap :red-copy-in-green-copy :blue :blue-copy-in-green-copy) + (tho/add-frame :frame-b2) + (thf/make-component :b2 :frame-b2))) + +(t/deftest test-keep-swap-slot-relocating-blue1-to-root + (let [;; ============================== Setup =============================== + file (setup-file) + page (thf/current-page file) + blue1 (thf/get-shape file :blue1) + + ;; ============================== Action ============================== + changes (cflh/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page) + #{(:parent-id blue1)} ;; parents + uuid/zero ;; paremt-id + (:id page) ;; page-id + 0 ;; to-index + #{(:id blue1)}) ;; ids + file' (thf/apply-changes file changes) + + ;; ============================== Get ================================= + blue1' (thf/get-shape file' :blue1)] + + ;; ================================== Check =============================== + ;; blue1 had swap-id before move + (t/is (some? (ctk/get-swap-slot blue1))) + + ;; blue1 has not swap-id after move + (t/is (some? blue1')) + (t/is (nil? (ctk/get-swap-slot blue1'))))) + + +(t/deftest test-keep-swap-slot-relocating-blue1-to-b2 + (let [;; ============================== Setup =============================== + file (setup-file) + page (thf/current-page file) + blue1 (thf/get-shape file :blue1) + b2 (thf/get-shape file :frame-b2) + + + ;; ============================== Action ============================== + changes (cflh/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page) + #{(:parent-id blue1)} ;; parents + (:id b2) ;; parent-id + (:id page) ;; page-id + 0 ;; to-index + #{(:id blue1)}) ;; ids + file' (thf/apply-changes file changes) + + ;; ============================== Get ================================= + blue1' (thf/get-shape file' :blue1)] + + ;; ================================== Check =============================== + ;; blue1 had swap-id before move + (t/is (some? (ctk/get-swap-slot blue1))) + + ;; blue1 has not swap-id after move + (t/is (some? blue1')) + (t/is (nil? (ctk/get-swap-slot blue1'))))) + +(t/deftest test-keep-swap-slot-relocating-yellow-to-root + (let [;; ============================== Setup =============================== + file (setup-file) + page (thf/current-page file) + blue1 (thf/get-shape file :blue1) + yellow (thf/get-shape file :frame-yellow) + + ;; ============================== Action ============================== + ;; Move blue1 into yellow + changes (cflh/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page) + #{(:parent-id blue1)} ;; parents + (:id yellow) ;; parent-id + (:id page) ;; page-id + 0 ;; to-index + #{(:id blue1)}) ;; ids + + file' (thf/apply-changes file changes) + page' (thf/current-page file') + yellow' (thf/get-shape file' :frame-yellow) + + ;; Move yellow into root + changes' (cflh/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page') + #{(:parent-id yellow')} ;; parents + uuid/zero ;; parent-id + (:id page') ;; page-id + 0 ;; to-index + #{(:id yellow')}) ;; ids + file'' (thf/apply-changes file' changes') + + ;; ============================== Get ================================= + blue1'' (thf/get-shape file'' :blue1)] + + ;; ================================== Check =============================== + ;; blue1 had swap-id before move + (t/is (some? (ctk/get-swap-slot blue1))) + + ;; blue1 has not swap-id after move + (t/is (some? blue1'')) + (t/is (nil? (ctk/get-swap-slot blue1''))))) + + +(t/deftest test-keep-swap-slot-relocating-yellow-to-b2 + (let [;; ============================== Setup =============================== + file (setup-file) + page (thf/current-page file) + blue1 (thf/get-shape file :blue1) + yellow (thf/get-shape file :frame-yellow) + + ;; ============================== Action ============================== + ;; Move blue1 into yellow + changes (cflh/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page) + #{(:parent-id blue1)} ;; parents + (:id yellow) ;; parent-id + (:id page) ;; page-id + 0 ;; to-index + #{(:id blue1)}) ;; ids + + file' (thf/apply-changes file changes) + page' (thf/current-page file') + yellow' (thf/get-shape file' :frame-yellow) + b2' (thf/get-shape file' :frame-b2) + + ;; Move yellow into b2 + changes' (cflh/generate-relocate-shapes (pcb/empty-changes nil) + (:objects page') + #{(:parent-id yellow')} ;; parents + (:id b2') ;; parent-id + (:id page') ;; page-id + 0 ;; to-index + #{(:id yellow')}) ;; ids + file'' (thf/apply-changes file' changes') + + ;; ============================== Get ================================= + blue1'' (thf/get-shape file'' :blue1)] + + ;; ================================== Check =============================== + ;; blue1 had swap-id before move + (t/is (some? (ctk/get-swap-slot blue1))) + + ;; blue1 has not swap-id after move + (t/is (some? blue1'')) + (t/is (nil? (ctk/get-swap-slot blue1''))))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 1f48ae4ed..52a09c8c9 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -13,6 +13,7 @@ [app.common.features :as cfeat] [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] + [app.common.files.libraries-helpers :as cflh] [app.common.geom.align :as gal] [app.common.geom.point :as gpt] [app.common.geom.proportions :as gpp] @@ -786,112 +787,6 @@ ;; --- Change Shape Order (D&D Ordering) -(defn relocate-shapes-changes [it objects parents parent-id page-id to-index ids - groups-to-delete groups-to-unmask shapes-to-detach - shapes-to-reroot shapes-to-deroot shapes-to-unconstraint] - (let [ordered-indexes (cfh/order-by-indexed-shapes objects ids) - shapes (map (d/getf objects) ordered-indexes) - parent (get objects parent-id) - component-main-parent (ctn/find-component-main objects parent false) - child-heads - (->> ordered-indexes - (mapcat #(ctn/get-child-heads objects %)) - (map :id))] - - (-> (pcb/empty-changes it 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 ordered-indexes ctl/remove-layout-item-data)) - - ;; Remove the hide in viewer flag - (cond-> (and (not= uuid/zero parent-id) (cfh/frame-shape? parent)) - (pcb/update-shapes ordered-indexes #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true)))) - - ;; Remove the swap slots if it is moving to a different component - (pcb/update-shapes child-heads - (fn [shape] - (cond-> shape - (not= component-main-parent (ctn/find-component-main objects shape false)) - (ctk/remove-swap-slot)))) - - ;; 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))) - - ;; Move the shapes - (pcb/change-parent parent-id - shapes - to-index) - - ;; Remove empty groups - (pcb/remove-objects groups-to-delete) - - ;; Unmask groups whose mask have moved outside - (pcb/update-shapes groups-to-unmask - (fn [shape] - (assoc shape :masked-group false))) - - ;; Detach shapes moved out of their component - (pcb/update-shapes shapes-to-detach ctk/detach-shape) - - ;; Make non root a component moved inside another one - (pcb/update-shapes shapes-to-deroot - (fn [shape] - (assoc shape :component-root nil))) - - ;; Make root a subcomponent moved outside its parent component - (pcb/update-shapes shapes-to-reroot - (fn [shape] - (assoc shape :component-root true))) - - ;; Reset constraints depending on the new parent - (pcb/update-shapes shapes-to-unconstraint - (fn [shape] - (let [frame-id (if (= (:type parent) :frame) - (:id parent) - (:frame-id parent)) - moved-shape (assoc shape - :parent-id parent-id - :frame-id frame-id)] - (assoc shape - :constraints-h (gsh/default-constraints-h moved-shape) - :constraints-v (gsh/default-constraints-v moved-shape)))) - {:ignore-touched true}) - - ;; Fix the sizing when moving a shape - (pcb/update-shapes parents - (fn [parent] - (if (ctl/flex-layout? parent) - (cond-> parent - (ctl/change-h-sizing? (:id parent) objects (:shapes parent)) - (assoc :layout-item-h-sizing :fix) - - (ctl/change-v-sizing? (:id parent) objects (:shapes parent)) - (assoc :layout-item-v-sizing :fix)) - parent))) - - ;; Update grid layout - (cond-> (ctl/grid-layout? objects parent-id) - (pcb/update-shapes [parent-id] #(ctl/add-children-to-index % ids objects to-index))) - - (pcb/update-shapes parents - (fn [parent objects] - (cond-> parent - (ctl/grid-layout? parent) - (ctl/assign-cells objects))) - {:with-objects? true}) - - (pcb/reorder-grid-children parents) - - ;; If parent locked, lock the added shapes - (cond-> (:blocked parent) - (pcb/update-shapes ordered-indexes #(assoc % :blocked true))) - - ;; Resize parent containers that need to - (pcb/resize-parents parents)))) - (defn relocate-shapes [ids parent-id to-index & [ignore-parents?]] (dm/assert! (every? uuid? ids)) @@ -913,97 +808,13 @@ all-parents (into #{parent-id} (map #(cfh/get-parent-id objects %)) ids) parents (if ignore-parents? #{parent-id} all-parents) - groups-to-delete - (loop [current-id (first parents) - to-check (rest parents) - removed-id? (set ids) - result #{}] - - (if-not current-id - ;; Base case, no next element - result - - (let [group (get objects current-id)] - (if (and (not= :frame (:type group)) - (not= current-id parent-id) - (empty? (remove removed-id? (:shapes group)))) - - ;; Adds group to the remove and check its parent - (let [to-check (concat to-check [(cfh/get-parent-id objects current-id)])] - (recur (first to-check) - (rest to-check) - (conj removed-id? current-id) - (conj result current-id))) - - ;; otherwise recur - (recur (first to-check) - (rest to-check) - removed-id? - result))))) - - groups-to-unmask - (reduce (fn [group-ids id] - ;; When a masked group loses its mask shape, because it's - ;; moved outside the group, the mask condition must be - ;; removed, and it must be converted to a normal group. - (let [obj (get objects id) - parent (get objects (:parent-id obj))] - (if (and (:masked-group parent) - (= id (first (:shapes parent))) - (not= (:id parent) parent-id)) - (conj group-ids (:id parent)) - group-ids))) - #{} - ids) - - ;; TODO: Probably implementing this using loop/recur will - ;; be more efficient than using reduce and continuous data - ;; desturcturing. - - ;; Sets the correct components metadata for the moved shapes - ;; `shapes-to-detach` Detach from a component instance a shape that was inside a component and is moved outside - ;; `shapes-to-deroot` Removes the root flag from a component instance moved inside another component - ;; `shapes-to-reroot` Adds a root flag when a nested component instance is moved outside - [shapes-to-detach shapes-to-deroot shapes-to-reroot] - (reduce (fn [[shapes-to-detach shapes-to-deroot shapes-to-reroot] id] - (let [shape (get objects id) - parent (get objects parent-id) - component-shape (ctn/get-component-shape objects shape) - component-shape-parent (ctn/get-component-shape objects parent {:allow-main? true}) - root-parent (ctn/get-instance-root objects parent) - - detach? (and (ctk/in-component-copy-not-head? shape) - (not= (:id component-shape) - (:id component-shape-parent))) - deroot? (and (ctk/instance-root? shape) - root-parent) - reroot? (and (ctk/subinstance-head? shape) - (not component-shape-parent)) - - ids-to-detach (when detach? - (cons id (cfh/get-children-ids objects id)))] - - [(cond-> shapes-to-detach detach? (into ids-to-detach)) - (cond-> shapes-to-deroot deroot? (conj id)) - (cond-> shapes-to-reroot reroot? (conj id))])) - [[] [] []] - (->> ids - (mapcat #(ctn/get-child-heads objects %)) - (map :id))) - - changes (relocate-shapes-changes it - objects - parents - parent-id - page-id - to-index - ids - groups-to-delete - groups-to-unmask - shapes-to-detach - shapes-to-reroot - shapes-to-deroot - ids) + changes (cflh/generate-relocate-shapes (pcb/empty-changes it) + objects + parents + parent-id + page-id + to-index + ids) undo-id (js/Symbol)] (rx/of (dwu/start-undo-transaction undo-id)