mirror of
https://github.com/penpot/penpot.git
synced 2025-05-29 23:46:12 +02:00
🐛 Fixes issues with moving shapes outside groups
This commit is contained in:
parent
c1a139fc51
commit
4d5231598f
4 changed files with 257 additions and 166 deletions
|
@ -360,7 +360,7 @@
|
||||||
(t/is (= [rect-a-id rect-e-id rect-d-id]
|
(t/is (= [rect-a-id rect-e-id rect-d-id]
|
||||||
(get-in objects [group-b-id :shapes]))))))
|
(get-in objects [group-b-id :shapes]))))))
|
||||||
|
|
||||||
(t/testing "Move elements and delete the empty group"
|
(t/testing "Move all elements from a group"
|
||||||
(let [changes [{:type :mov-objects
|
(let [changes [{:type :mov-objects
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:parent-id group-a-id
|
:parent-id group-a-id
|
||||||
|
@ -368,9 +368,9 @@
|
||||||
res (cp/process-changes data changes)]
|
res (cp/process-changes data changes)]
|
||||||
|
|
||||||
(let [objects (get-in res [:pages-index page-id :objects])]
|
(let [objects (get-in res [:pages-index page-id :objects])]
|
||||||
(t/is (= [group-a-id rect-e-id]
|
(t/is (= [group-a-id group-b-id rect-e-id]
|
||||||
(get-in objects [frame-a-id :shapes])))
|
(get-in objects [frame-a-id :shapes])))
|
||||||
(t/is (nil? (get-in objects [group-b-id]))))))
|
(t/is (empty? (get-in objects [group-b-id :shapes]))))))
|
||||||
|
|
||||||
(t/testing "Move elements to a group with different frame"
|
(t/testing "Move elements to a group with different frame"
|
||||||
(let [changes [{:type :mov-objects
|
(let [changes [{:type :mov-objects
|
||||||
|
@ -727,11 +727,11 @@
|
||||||
|
|
||||||
;; After
|
;; After
|
||||||
|
|
||||||
(t/is (= [shape-2-id shape-1-id shape-3-id shape-4-id]
|
(t/is (= [shape-2-id shape-1-id shape-3-id shape-4-id group-1-id]
|
||||||
(get-in res [:pages-index page-id :objects cp/root :shapes])))
|
(get-in res [:pages-index page-id :objects cp/root :shapes])))
|
||||||
|
|
||||||
(t/is (= nil
|
(t/is (not= nil
|
||||||
(get-in res [:pages-index page-id :objects group-1-id])))
|
(get-in res [:pages-index page-id :objects group-1-id])))
|
||||||
|
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,20 @@
|
||||||
(when verify?
|
(when verify?
|
||||||
(us/verify ::spec/changes items))
|
(us/verify ::spec/changes items))
|
||||||
|
|
||||||
(->> items
|
(let [pages (into #{} (map :page-id) items)
|
||||||
(reduce #(or (process-change %1 %2) %1) data))))
|
result (->> items
|
||||||
|
(reduce #(or (process-change %1 %2) %1) data))]
|
||||||
|
|
||||||
|
;; Validate result shapes (only on the backend)
|
||||||
|
#?(:clj
|
||||||
|
(doseq [page-id pages]
|
||||||
|
(let [page (get-in result [:pages-index page-id])]
|
||||||
|
(doseq [[id shape] (:objects page)]
|
||||||
|
(if-not (= shape (get-in data [:pages-index page-id :objects id]))
|
||||||
|
;; If object has change verify is correct
|
||||||
|
(us/verify ::spec/shape shape))))))
|
||||||
|
|
||||||
|
result)))
|
||||||
|
|
||||||
(defmethod process-change :set-option
|
(defmethod process-change :set-option
|
||||||
[data {:keys [page-id option value]}]
|
[data {:keys [page-id option value]}]
|
||||||
|
@ -94,7 +106,6 @@
|
||||||
(let [update-fn (fn [objects]
|
(let [update-fn (fn [objects]
|
||||||
(if-let [obj (get objects id)]
|
(if-let [obj (get objects id)]
|
||||||
(let [result (reduce process-operation obj operations)]
|
(let [result (reduce process-operation obj operations)]
|
||||||
#?(:clj (us/verify ::spec/shape result))
|
|
||||||
(assoc objects id result))
|
(assoc objects id result))
|
||||||
objects))]
|
objects))]
|
||||||
(if page-id
|
(if page-id
|
||||||
|
@ -142,16 +153,25 @@
|
||||||
(map :id)
|
(map :id)
|
||||||
(distinct))
|
(distinct))
|
||||||
shapes)))
|
shapes)))
|
||||||
|
(set-mask-selrect [group children]
|
||||||
|
(let [mask (first children)]
|
||||||
|
(-> group
|
||||||
|
(merge (select-keys mask [:selrect :points]))
|
||||||
|
(assoc :x (-> mask :selrect :x)
|
||||||
|
:y (-> mask :selrect :y)
|
||||||
|
:width (-> mask :selrect :width)
|
||||||
|
:height (-> mask :selrect :height)))))
|
||||||
(update-group [group objects]
|
(update-group [group objects]
|
||||||
(let [children (->> group :shapes (map #(get objects %)))]
|
(let [children (->> group :shapes (map #(get objects %)))]
|
||||||
(if (:masked-group? group)
|
(cond
|
||||||
(let [mask (first children)]
|
;; If the group is empty we don't make any changes. Should be removed by a later process
|
||||||
(-> group
|
(empty? children)
|
||||||
(merge (select-keys mask [:selrect :points]))
|
group
|
||||||
(assoc :x (-> mask :selrect :x)
|
|
||||||
:y (-> mask :selrect :y)
|
(:masked-group? group)
|
||||||
:width (-> mask :selrect :width)
|
(set-mask-selrect group children)
|
||||||
:height (-> mask :selrect :height))))
|
|
||||||
|
:else
|
||||||
(gsh/update-group-selrect group children))))]
|
(gsh/update-group-selrect group children))))]
|
||||||
|
|
||||||
(if page-id
|
(if page-id
|
||||||
|
@ -206,23 +226,17 @@
|
||||||
pid prev-parent-id
|
pid prev-parent-id
|
||||||
objects objects]
|
objects objects]
|
||||||
(let [obj (get objects pid)]
|
(let [obj (get objects pid)]
|
||||||
(if (and (= 1 (count (:shapes obj)))
|
(cond-> objects
|
||||||
(= sid (first (:shapes obj)))
|
true
|
||||||
(= :group (:type obj)))
|
(update-in [pid :shapes] strip-id sid)
|
||||||
(recur pid
|
|
||||||
(:parent-id obj)
|
|
||||||
(dissoc objects pid))
|
|
||||||
(cond-> objects
|
|
||||||
true
|
|
||||||
(update-in [pid :shapes] strip-id sid)
|
|
||||||
|
|
||||||
(and (:shape-ref obj)
|
(and (:shape-ref obj)
|
||||||
(= (:type obj) :group)
|
(= (:type obj) :group)
|
||||||
(not ignore-touched))
|
(not ignore-touched))
|
||||||
(->
|
(->
|
||||||
(update-in [pid :touched]
|
(update-in [pid :touched]
|
||||||
cph/set-touched-group :shapes-group)
|
cph/set-touched-group :shapes-group)
|
||||||
(d/dissoc-in [pid :remote-synced?])))))))))
|
(d/dissoc-in [pid :remote-synced?]))))))))
|
||||||
|
|
||||||
(update-parent-id [objects id]
|
(update-parent-id [objects id]
|
||||||
(assoc-in objects [id :parent-id] parent-id))
|
(assoc-in objects [id :parent-id] parent-id))
|
||||||
|
|
|
@ -808,6 +808,168 @@
|
||||||
|
|
||||||
;; --- Change Shape Order (D&D Ordering)
|
;; --- Change Shape Order (D&D Ordering)
|
||||||
|
|
||||||
|
(defn relocate-shapes-changes [objects parents parent-id page-id to-index ids groups-to-delete groups-to-unmask shapes-to-detach shapes-to-reroot shapes-to-deroot]
|
||||||
|
(let [;; Changes to the shapes that are being move
|
||||||
|
r-mov-change
|
||||||
|
[{:type :mov-objects
|
||||||
|
:parent-id parent-id
|
||||||
|
:page-id page-id
|
||||||
|
:index to-index
|
||||||
|
:shapes (vec (reverse ids))}]
|
||||||
|
|
||||||
|
u-mov-change
|
||||||
|
(map (fn [id]
|
||||||
|
(let [obj (get objects id)]
|
||||||
|
{:type :mov-objects
|
||||||
|
:parent-id (:parent-id obj)
|
||||||
|
:page-id page-id
|
||||||
|
:index (cp/position-on-parent id objects)
|
||||||
|
:shapes [id]}))
|
||||||
|
(reverse ids))
|
||||||
|
|
||||||
|
;; Changes deleting empty groups
|
||||||
|
r-del-change
|
||||||
|
(map (fn [group-id]
|
||||||
|
{:type :del-obj
|
||||||
|
:page-id page-id
|
||||||
|
:id group-id})
|
||||||
|
groups-to-delete)
|
||||||
|
|
||||||
|
u-del-change
|
||||||
|
(d/concat
|
||||||
|
[]
|
||||||
|
;; Create the groups
|
||||||
|
(map (fn [group-id]
|
||||||
|
(let [group (get objects group-id)]
|
||||||
|
{:type :add-obj
|
||||||
|
:page-id page-id
|
||||||
|
:parent-id parent-id
|
||||||
|
:frame-id (:frame-id group)
|
||||||
|
:id group-id
|
||||||
|
:obj (-> group
|
||||||
|
(assoc :shapes []))}))
|
||||||
|
groups-to-delete)
|
||||||
|
;; Creates the hierarchy
|
||||||
|
(map (fn [group-id]
|
||||||
|
(let [group (get objects group-id)]
|
||||||
|
{:type :mov-objects
|
||||||
|
:page-id page-id
|
||||||
|
:parent-id (:id group)
|
||||||
|
:shapes (:shapes group)}))
|
||||||
|
groups-to-delete))
|
||||||
|
|
||||||
|
;; Changes removing the masks from the groups without mask shape
|
||||||
|
r-mask-change
|
||||||
|
(map (fn [group-id]
|
||||||
|
{:type :mod-obj
|
||||||
|
:page-id page-id
|
||||||
|
:id group-id
|
||||||
|
:operations [{:type :set
|
||||||
|
:attr :masked-group?
|
||||||
|
:val false}]})
|
||||||
|
groups-to-unmask)
|
||||||
|
|
||||||
|
u-mask-change
|
||||||
|
(map (fn [group-id]
|
||||||
|
(let [group (get objects group-id)]
|
||||||
|
{:type :mod-obj
|
||||||
|
:page-id page-id
|
||||||
|
:id group-id
|
||||||
|
:operations [{:type :set
|
||||||
|
:attr :masked-group?
|
||||||
|
:val (:masked-group? group)}]}))
|
||||||
|
groups-to-unmask)
|
||||||
|
|
||||||
|
;; Changes to the components metadata
|
||||||
|
|
||||||
|
detach-keys [:component-id :component-file :component-root? :remote-synced? :shape-ref :touched]
|
||||||
|
|
||||||
|
r-detach-change
|
||||||
|
(map (fn [id]
|
||||||
|
{:type :mod-obj
|
||||||
|
:page-id page-id
|
||||||
|
:id id
|
||||||
|
:operations (mapv #(hash-map :type :set :attr % :val nil) detach-keys)})
|
||||||
|
shapes-to-detach)
|
||||||
|
|
||||||
|
u-detach-change
|
||||||
|
(map (fn [id]
|
||||||
|
(let [obj (get objects id)]
|
||||||
|
{:type :mod-obj
|
||||||
|
:page-id page-id
|
||||||
|
:id id
|
||||||
|
:operations (mapv #(hash-map :type :set :attr % :val (get obj %)) detach-keys)}))
|
||||||
|
shapes-to-detach)
|
||||||
|
|
||||||
|
r-deroot-change
|
||||||
|
(map (fn [id]
|
||||||
|
{:type :mod-obj
|
||||||
|
:page-id page-id
|
||||||
|
:id id
|
||||||
|
:operations [{:type :set
|
||||||
|
:attr :component-root?
|
||||||
|
:val nil}]})
|
||||||
|
shapes-to-deroot)
|
||||||
|
|
||||||
|
u-deroot-change
|
||||||
|
(map (fn [id]
|
||||||
|
{:type :mod-obj
|
||||||
|
:page-id page-id
|
||||||
|
:id id
|
||||||
|
:operations [{:type :set
|
||||||
|
:attr :component-root?
|
||||||
|
:val true}]})
|
||||||
|
shapes-to-deroot)
|
||||||
|
|
||||||
|
r-reroot-change
|
||||||
|
(map (fn [id]
|
||||||
|
{:type :mod-obj
|
||||||
|
:page-id page-id
|
||||||
|
:id id
|
||||||
|
:operations [{:type :set
|
||||||
|
:attr :component-root?
|
||||||
|
:val true}]})
|
||||||
|
shapes-to-reroot)
|
||||||
|
|
||||||
|
u-reroot-change
|
||||||
|
(map (fn [id]
|
||||||
|
{:type :mod-obj
|
||||||
|
:page-id page-id
|
||||||
|
:id id
|
||||||
|
:operations [{:type :set
|
||||||
|
:attr :component-root?
|
||||||
|
:val nil}]})
|
||||||
|
shapes-to-reroot)
|
||||||
|
|
||||||
|
r-reg-change
|
||||||
|
[{:type :reg-objects
|
||||||
|
:page-id page-id
|
||||||
|
:shapes (vec parents)}]
|
||||||
|
|
||||||
|
u-reg-change
|
||||||
|
[{:type :reg-objects
|
||||||
|
:page-id page-id
|
||||||
|
:shapes (vec parents)}]
|
||||||
|
|
||||||
|
rchanges (d/concat []
|
||||||
|
r-mov-change
|
||||||
|
r-del-change
|
||||||
|
r-mask-change
|
||||||
|
r-detach-change
|
||||||
|
r-deroot-change
|
||||||
|
r-reroot-change
|
||||||
|
r-reg-change)
|
||||||
|
|
||||||
|
uchanges (d/concat []
|
||||||
|
u-del-change
|
||||||
|
u-reroot-change
|
||||||
|
u-deroot-change
|
||||||
|
u-detach-change
|
||||||
|
u-mask-change
|
||||||
|
u-mov-change
|
||||||
|
u-reg-change)]
|
||||||
|
[rchanges uchanges]))
|
||||||
|
|
||||||
(defn relocate-shapes
|
(defn relocate-shapes
|
||||||
[ids parent-id to-index]
|
[ids parent-id to-index]
|
||||||
(us/verify (s/coll-of ::us/uuid) ids)
|
(us/verify (s/coll-of ::us/uuid) ids)
|
||||||
|
@ -826,13 +988,37 @@
|
||||||
;; If we try to move a parent into a child we remove it
|
;; If we try to move a parent into a child we remove it
|
||||||
ids (filter #(not (cp/is-parent? objects parent-id %)) ids)
|
ids (filter #(not (cp/is-parent? objects parent-id %)) ids)
|
||||||
|
|
||||||
parents (loop [res #{parent-id}
|
parents (reduce (fn [result id]
|
||||||
ids (seq ids)]
|
(conj result (cp/get-parent id objects)))
|
||||||
(if (nil? ids)
|
#{parent-id} ids)
|
||||||
(vec res)
|
|
||||||
(recur
|
groups-to-delete
|
||||||
(conj res (cp/get-parent (first ids) objects))
|
(loop [current-id (first parents)
|
||||||
(next ids))))
|
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= uuid/zero current-id)
|
||||||
|
(not= current-id parent-id)
|
||||||
|
(empty? (remove removed-id? (:shapes group))))
|
||||||
|
|
||||||
|
;; Adds group to the remove and check its parent
|
||||||
|
(let [to-check (d/concat [] to-check [(cp/get-parent current-id objects)]) ]
|
||||||
|
(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
|
groups-to-unmask
|
||||||
(reduce (fn [group-ids id]
|
(reduce (fn [group-ids id]
|
||||||
|
@ -849,6 +1035,10 @@
|
||||||
#{}
|
#{}
|
||||||
ids)
|
ids)
|
||||||
|
|
||||||
|
;; 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]
|
[shapes-to-detach shapes-to-deroot shapes-to-reroot]
|
||||||
(reduce (fn [[shapes-to-detach shapes-to-deroot shapes-to-reroot] id]
|
(reduce (fn [[shapes-to-detach shapes-to-deroot shapes-to-reroot] id]
|
||||||
(let [shape (get objects id)
|
(let [shape (get objects id)
|
||||||
|
@ -876,131 +1066,18 @@
|
||||||
[[] [] []]
|
[[] [] []]
|
||||||
ids)
|
ids)
|
||||||
|
|
||||||
rchanges (d/concat
|
[rchanges uchanges] (relocate-shapes-changes objects
|
||||||
[{:type :mov-objects
|
parents
|
||||||
:parent-id parent-id
|
parent-id
|
||||||
:page-id page-id
|
page-id
|
||||||
:index to-index
|
to-index
|
||||||
:shapes (vec (reverse ids))}
|
ids
|
||||||
{:type :reg-objects
|
groups-to-delete
|
||||||
:page-id page-id
|
groups-to-unmask
|
||||||
:shapes parents}]
|
shapes-to-detach
|
||||||
(map (fn [group-id]
|
shapes-to-reroot
|
||||||
{:type :mod-obj
|
shapes-to-deroot)]
|
||||||
:page-id page-id
|
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||||
:id group-id
|
|
||||||
:operations [{:type :set
|
|
||||||
:attr :masked-group?
|
|
||||||
:val false}]})
|
|
||||||
groups-to-unmask)
|
|
||||||
(map (fn [id]
|
|
||||||
{:type :mod-obj
|
|
||||||
:page-id page-id
|
|
||||||
:id id
|
|
||||||
:operations [{:type :set
|
|
||||||
:attr :component-id
|
|
||||||
:val nil}
|
|
||||||
{:type :set
|
|
||||||
:attr :component-file
|
|
||||||
:val nil}
|
|
||||||
{:type :set
|
|
||||||
:attr :component-root?
|
|
||||||
:val nil}
|
|
||||||
{:type :set
|
|
||||||
:attr :remote-synced?
|
|
||||||
:val nil}
|
|
||||||
{:type :set
|
|
||||||
:attr :shape-ref
|
|
||||||
:val nil}
|
|
||||||
{:type :set
|
|
||||||
:attr :touched
|
|
||||||
:val nil}]})
|
|
||||||
shapes-to-detach)
|
|
||||||
(map (fn [id]
|
|
||||||
{:type :mod-obj
|
|
||||||
:page-id page-id
|
|
||||||
:id id
|
|
||||||
:operations [{:type :set
|
|
||||||
:attr :component-root?
|
|
||||||
:val nil}]})
|
|
||||||
shapes-to-deroot)
|
|
||||||
(map (fn [id]
|
|
||||||
{:type :mod-obj
|
|
||||||
:page-id page-id
|
|
||||||
:id id
|
|
||||||
:operations [{:type :set
|
|
||||||
:attr :component-root?
|
|
||||||
:val true}]})
|
|
||||||
shapes-to-reroot))
|
|
||||||
|
|
||||||
uchanges (d/concat
|
|
||||||
(reduce (fn [res id]
|
|
||||||
(let [obj (get objects id)]
|
|
||||||
(conj res
|
|
||||||
{:type :mov-objects
|
|
||||||
:parent-id (:parent-id obj)
|
|
||||||
:page-id page-id
|
|
||||||
:index (cp/position-on-parent id objects)
|
|
||||||
:shapes [id]})))
|
|
||||||
[] (reverse ids))
|
|
||||||
[{:type :reg-objects
|
|
||||||
:page-id page-id
|
|
||||||
:shapes parents}]
|
|
||||||
(map (fn [group-id]
|
|
||||||
{:type :mod-obj
|
|
||||||
:page-id page-id
|
|
||||||
:id group-id
|
|
||||||
:operations [{:type :set
|
|
||||||
:attr :masked-group?
|
|
||||||
:val true}]})
|
|
||||||
groups-to-unmask)
|
|
||||||
(map (fn [id]
|
|
||||||
(let [obj (get objects id)]
|
|
||||||
{:type :mod-obj
|
|
||||||
:page-id page-id
|
|
||||||
:id id
|
|
||||||
:operations [{:type :set
|
|
||||||
:attr :component-id
|
|
||||||
:val (:component-id obj)}
|
|
||||||
{:type :set
|
|
||||||
:attr :component-file
|
|
||||||
:val (:component-file obj)}
|
|
||||||
{:type :set
|
|
||||||
:attr :component-root?
|
|
||||||
:val (:component-root? obj)}
|
|
||||||
{:type :set
|
|
||||||
:attr :remote-synced?
|
|
||||||
:val (:remote-synced? obj)}
|
|
||||||
{:type :set
|
|
||||||
:attr :shape-ref
|
|
||||||
:val (:shape-ref obj)}
|
|
||||||
{:type :set
|
|
||||||
:attr :touched
|
|
||||||
:val (:touched obj)}]}))
|
|
||||||
shapes-to-detach)
|
|
||||||
(map (fn [id]
|
|
||||||
{:type :mod-obj
|
|
||||||
:page-id page-id
|
|
||||||
:id id
|
|
||||||
:operations [{:type :set
|
|
||||||
:attr :component-root?
|
|
||||||
:val true}]})
|
|
||||||
shapes-to-deroot)
|
|
||||||
(map (fn [id]
|
|
||||||
{:type :mod-obj
|
|
||||||
:page-id page-id
|
|
||||||
:id id
|
|
||||||
:operations [{:type :set
|
|
||||||
:attr :component-root?
|
|
||||||
:val nil}]})
|
|
||||||
shapes-to-reroot))]
|
|
||||||
|
|
||||||
;; (println "================ rchanges")
|
|
||||||
;; (cljs.pprint/pprint rchanges)
|
|
||||||
;; (println "================ uchanges")
|
|
||||||
;; (cljs.pprint/pprint uchanges)
|
|
||||||
(rx/of (dwc/commit-changes rchanges uchanges
|
|
||||||
{:commit-local? true})
|
|
||||||
(dwc/expand-collapse parent-id))))))
|
(dwc/expand-collapse parent-id))))))
|
||||||
|
|
||||||
(defn relocate-selected-shapes
|
(defn relocate-selected-shapes
|
||||||
|
|
|
@ -198,7 +198,7 @@
|
||||||
|
|
||||||
(defn retrieve-used-names
|
(defn retrieve-used-names
|
||||||
[objects]
|
[objects]
|
||||||
(into #{} (map :name) (vals objects)))
|
(into #{} (comp (map :name) (remove nil?)) (vals objects)))
|
||||||
|
|
||||||
|
|
||||||
(defn generate-unique-name
|
(defn generate-unique-name
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue