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:
parent
78d0611632
commit
f354942487
7 changed files with 499 additions and 229 deletions
|
@ -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))))
|
||||
|
|
|
@ -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]
|
||||
|
|
33
common/test/common_tests/helpers/debug.cljc
Normal file
33
common/test/common_tests/helpers/debug.cljc
Normal 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)))))
|
|
@ -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}]
|
||||
|
|
|
@ -34,3 +34,9 @@
|
|||
[f]
|
||||
(reset-idmap!)
|
||||
(f))
|
||||
|
||||
(defn label [id]
|
||||
(->> @idmap
|
||||
(filter #(= id (val %)))
|
||||
(map key)
|
||||
(first)))
|
||||
|
|
194
common/test/common_tests/logic/comp_remove_swap_slots_test.cljc
Normal file
194
common/test/common_tests/logic/comp_remove_swap_slots_test.cljc
Normal 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'')))))
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue