0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-28 15:41:25 -05:00

♻️ Components refactor: generator for relocate shapes (and tests)

This commit is contained in:
Pablo Alba 2024-04-29 16:37:21 +02:00 committed by Andrés Moya
parent 78d0611632
commit f354942487
7 changed files with 499 additions and 229 deletions

View file

@ -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))))

View file

@ -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]

View file

@ -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)))))

View file

@ -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}]

View file

@ -34,3 +34,9 @@
[f]
(reset-idmap!)
(f))
(defn label [id]
(->> @idmap
(filter #(= id (val %)))
(map key)
(first)))

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.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'')))))

View file

@ -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)