mirror of
https://github.com/penpot/penpot.git
synced 2025-05-19 09:16:10 +02:00
🐛 Fix ungroup component
This commit is contained in:
parent
128fe29619
commit
7733bc4419
3 changed files with 167 additions and 101 deletions
|
@ -328,6 +328,9 @@
|
||||||
The list of objects are returned in tree traversal order, respecting
|
The list of objects are returned in tree traversal order, respecting
|
||||||
the order of the children of each parent."
|
the order of the children of each parent."
|
||||||
|
|
||||||
|
([object parent-id objects]
|
||||||
|
(clone-object object parent-id objects (fn [object _] object) (fn [object _] object) nil false))
|
||||||
|
|
||||||
([object parent-id objects update-new-object]
|
([object parent-id objects update-new-object]
|
||||||
(clone-object object parent-id objects update-new-object (fn [object _] object) nil false))
|
(clone-object object parent-id objects update-new-object (fn [object _] object) nil false))
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,14 @@
|
||||||
[app.common.pages.changes-builder :as pcb]
|
[app.common.pages.changes-builder :as pcb]
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
[app.common.types.component :as ctk]
|
[app.common.types.component :as ctk]
|
||||||
|
[app.common.types.pages-list :as ctpl]
|
||||||
[app.common.types.shape :as cts]
|
[app.common.types.shape :as cts]
|
||||||
|
[app.common.types.shape-tree :as ctst]
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
[app.main.data.workspace.changes :as dch]
|
[app.main.data.workspace.changes :as dch]
|
||||||
[app.main.data.workspace.selection :as dws]
|
[app.main.data.workspace.selection :as dws]
|
||||||
|
[app.main.data.workspace.shapes :as dwsh]
|
||||||
[app.main.data.workspace.state-helpers :as wsh]
|
[app.main.data.workspace.state-helpers :as wsh]
|
||||||
[app.main.data.workspace.undo :as dwu]
|
[app.main.data.workspace.undo :as dwu]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
|
@ -140,7 +144,7 @@
|
||||||
(mapv #(get objects %)))
|
(mapv #(get objects %)))
|
||||||
parent-id (cph/get-parent-id objects (:id frame))
|
parent-id (cph/get-parent-id objects (:id frame))
|
||||||
idx-in-parent (->> (:id frame)
|
idx-in-parent (->> (:id frame)
|
||||||
(cph/get-position-on-parent objects)
|
(cph/get-position-on-parent objects)
|
||||||
inc)]
|
inc)]
|
||||||
|
|
||||||
(-> (pcb/empty-changes it page-id)
|
(-> (pcb/empty-changes it page-id)
|
||||||
|
@ -150,6 +154,46 @@
|
||||||
(pcb/change-parent parent-id children idx-in-parent)
|
(pcb/change-parent parent-id children idx-in-parent)
|
||||||
(pcb/remove-objects [(:id frame)]))))
|
(pcb/remove-objects [(:id frame)]))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- clone-component-shapes-changes
|
||||||
|
[changes shape objects]
|
||||||
|
(let [shape-parent-id (:parent-id shape)
|
||||||
|
new-shape-id (uuid/next)
|
||||||
|
[_ new-shapes _]
|
||||||
|
(ctst/clone-object shape
|
||||||
|
shape-parent-id
|
||||||
|
objects
|
||||||
|
(fn [object _]
|
||||||
|
(cond-> object
|
||||||
|
(= new-shape-id (:parent-id object))
|
||||||
|
(assoc :parent-id shape-parent-id)))
|
||||||
|
(fn [object _] object)
|
||||||
|
new-shape-id
|
||||||
|
false)
|
||||||
|
|
||||||
|
new-shapes (->> new-shapes
|
||||||
|
(filter #(not= (:id %) new-shape-id)))]
|
||||||
|
(reduce
|
||||||
|
(fn [changes shape]
|
||||||
|
(pcb/add-object changes shape))
|
||||||
|
changes
|
||||||
|
new-shapes)))
|
||||||
|
|
||||||
|
(defn remove-component-changes
|
||||||
|
[it page-id shape objects file-data file]
|
||||||
|
(let [page (ctpl/get-page file-data page-id)
|
||||||
|
components-v2 (dm/get-in file-data [:options :components-v2])
|
||||||
|
;; In order to ungroup a component, we first make a clone of its shapes,
|
||||||
|
;; and then we delete it
|
||||||
|
changes (-> (pcb/empty-changes it page-id)
|
||||||
|
(pcb/with-objects objects)
|
||||||
|
(pcb/with-library-data file-data)
|
||||||
|
(pcb/with-page page)
|
||||||
|
(clone-component-shapes-changes shape objects)
|
||||||
|
(dwsh/delete-shapes-changes file page objects [(:id shape)] it components-v2))]
|
||||||
|
;; TODO: Should we call detach-comment-thread ?
|
||||||
|
changes))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; GROUPS
|
;; GROUPS
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -173,13 +217,18 @@
|
||||||
(ptk/reify ::ungroup-selected
|
(ptk/reify ::ungroup-selected
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [it state _]
|
(watch [it state _]
|
||||||
(let [page-id (:current-page-id state)
|
(let [page-id (:current-page-id state)
|
||||||
objects (wsh/lookup-page-objects state page-id)
|
objects (wsh/lookup-page-objects state page-id)
|
||||||
|
file-data (get state :workspace-data)
|
||||||
|
file (wsh/get-local-file state)
|
||||||
|
|
||||||
prepare
|
prepare
|
||||||
(fn [shape-id]
|
(fn [shape-id]
|
||||||
(let [shape (get objects shape-id)]
|
(let [shape (get objects shape-id)]
|
||||||
(cond
|
(cond
|
||||||
|
(ctk/main-instance? shape)
|
||||||
|
(remove-component-changes it page-id shape objects file-data file)
|
||||||
|
|
||||||
(or (cph/group-shape? shape) (cph/bool-shape? shape))
|
(or (cph/group-shape? shape) (cph/bool-shape? shape))
|
||||||
(remove-group-changes it page-id shape objects)
|
(remove-group-changes it page-id shape objects)
|
||||||
|
|
||||||
|
|
|
@ -214,126 +214,140 @@
|
||||||
(real-delete-shapes file page objects ids-to-delete it components-v2)
|
(real-delete-shapes file page objects ids-to-delete it components-v2)
|
||||||
(rx/of (dwu/commit-undo-transaction undo-id))))))))
|
(rx/of (dwu/commit-undo-transaction undo-id))))))))
|
||||||
|
|
||||||
(defn- real-delete-shapes
|
(defn- real-delete-shapes-changes
|
||||||
[file page objects ids it components-v2]
|
([file page objects ids it components-v2]
|
||||||
(let [lookup (d/getf objects)
|
(let [changes (-> (pcb/empty-changes it (:id page))
|
||||||
|
(pcb/with-page page)
|
||||||
groups-to-unmask
|
(pcb/with-objects objects)
|
||||||
(reduce (fn [group-ids id]
|
(pcb/with-library-data file))]
|
||||||
|
(real-delete-shapes-changes changes file page objects ids it components-v2)))
|
||||||
|
([changes file page objects ids _it components-v2]
|
||||||
|
(let [lookup (d/getf objects)
|
||||||
|
groups-to-unmask
|
||||||
|
(reduce (fn [group-ids id]
|
||||||
;; When the shape to delete is the mask of a masked group,
|
;; When the shape to delete is the mask of a masked group,
|
||||||
;; the mask condition must be removed, and it must be
|
;; the mask condition must be removed, and it must be
|
||||||
;; converted to a normal group.
|
;; converted to a normal group.
|
||||||
(let [obj (lookup id)
|
(let [obj (lookup id)
|
||||||
parent (lookup (:parent-id obj))]
|
parent (lookup (:parent-id obj))]
|
||||||
(if (and (:masked-group? parent)
|
(if (and (:masked-group? parent)
|
||||||
(= id (first (:shapes parent))))
|
(= id (first (:shapes parent))))
|
||||||
(conj group-ids (:id parent))
|
(conj group-ids (:id parent))
|
||||||
group-ids)))
|
group-ids)))
|
||||||
#{}
|
#{}
|
||||||
ids)
|
ids)
|
||||||
|
|
||||||
interacting-shapes
|
interacting-shapes
|
||||||
(filter (fn [shape]
|
(filter (fn [shape]
|
||||||
;; If any of the deleted shapes is the destination of
|
;; If any of the deleted shapes is the destination of
|
||||||
;; some interaction, this must be deleted, too.
|
;; some interaction, this must be deleted, too.
|
||||||
(let [interactions (:interactions shape)]
|
(let [interactions (:interactions shape)]
|
||||||
(some #(and (ctsi/has-destination %)
|
(some #(and (ctsi/has-destination %)
|
||||||
(contains? ids (:destination %)))
|
(contains? ids (:destination %)))
|
||||||
interactions)))
|
interactions)))
|
||||||
(vals objects))
|
(vals objects))
|
||||||
|
|
||||||
;; If any of the deleted shapes is a frame with guides
|
;; If any of the deleted shapes is a frame with guides
|
||||||
guides (into {}
|
guides (into {}
|
||||||
(comp (map second)
|
(comp (map second)
|
||||||
(remove #(contains? ids (:frame-id %)))
|
(remove #(contains? ids (:frame-id %)))
|
||||||
(map (juxt :id identity)))
|
(map (juxt :id identity)))
|
||||||
(dm/get-in page [:options :guides]))
|
(dm/get-in page [:options :guides]))
|
||||||
|
|
||||||
starting-flows
|
starting-flows
|
||||||
(filter (fn [flow]
|
(filter (fn [flow]
|
||||||
;; If any of the deleted is a frame that starts a flow,
|
;; If any of the deleted is a frame that starts a flow,
|
||||||
;; this must be deleted, too.
|
;; this must be deleted, too.
|
||||||
(contains? ids (:starting-frame flow)))
|
(contains? ids (:starting-frame flow)))
|
||||||
(-> page :options :flows))
|
(-> page :options :flows))
|
||||||
|
|
||||||
all-parents
|
all-parents
|
||||||
(reduce (fn [res id]
|
(reduce (fn [res id]
|
||||||
;; All parents of any deleted shape must be resized.
|
;; All parents of any deleted shape must be resized.
|
||||||
(into res (cph/get-parent-ids objects id)))
|
(into res (cph/get-parent-ids objects id)))
|
||||||
(d/ordered-set)
|
(d/ordered-set)
|
||||||
ids)
|
ids)
|
||||||
|
|
||||||
all-children
|
all-children
|
||||||
(->> ids ;; Children of deleted shapes must be also deleted.
|
(->> ids ;; Children of deleted shapes must be also deleted.
|
||||||
(reduce (fn [res id]
|
(reduce (fn [res id]
|
||||||
(into res (cph/get-children-ids objects id)))
|
(into res (cph/get-children-ids objects id)))
|
||||||
[])
|
[])
|
||||||
(reverse)
|
(reverse)
|
||||||
(into (d/ordered-set)))
|
(into (d/ordered-set)))
|
||||||
|
|
||||||
find-all-empty-parents
|
find-all-empty-parents
|
||||||
(fn recursive-find-empty-parents [empty-parents]
|
(fn recursive-find-empty-parents [empty-parents]
|
||||||
(let [all-ids (into empty-parents ids)
|
(let [all-ids (into empty-parents ids)
|
||||||
contains? (partial contains? all-ids)
|
contains? (partial contains? all-ids)
|
||||||
xform (comp (map lookup)
|
xform (comp (map lookup)
|
||||||
(filter cph/group-shape?)
|
(filter cph/group-shape?)
|
||||||
(remove #(->> (:shapes %) (remove contains?) seq))
|
(remove #(->> (:shapes %) (remove contains?) seq))
|
||||||
(map :id))
|
(map :id))
|
||||||
parents (into #{} xform all-parents)]
|
parents (into #{} xform all-parents)]
|
||||||
(if (= empty-parents parents)
|
(if (= empty-parents parents)
|
||||||
empty-parents
|
empty-parents
|
||||||
(recursive-find-empty-parents parents))))
|
(recursive-find-empty-parents parents))))
|
||||||
|
|
||||||
empty-parents
|
empty-parents
|
||||||
;; Any parent whose children are all deleted, must be deleted too.
|
;; Any parent whose children are all deleted, must be deleted too.
|
||||||
(into (d/ordered-set) (find-all-empty-parents #{}))
|
(into (d/ordered-set) (find-all-empty-parents #{}))
|
||||||
|
|
||||||
components-to-delete
|
components-to-delete
|
||||||
(if components-v2
|
(if components-v2
|
||||||
(reduce (fn [components id]
|
(reduce (fn [components id]
|
||||||
(let [shape (get objects id)]
|
(let [shape (get objects id)]
|
||||||
(if (and (= (:component-file shape) (:id file)) ;; Main instances should exist only in local file
|
(if (and (= (:component-file shape) (:id file)) ;; Main instances should exist only in local file
|
||||||
(:main-instance? shape)) ;; but check anyway
|
(:main-instance? shape)) ;; but check anyway
|
||||||
(conj components (:component-id shape))
|
(conj components (:component-id shape))
|
||||||
components)))
|
components)))
|
||||||
[]
|
[]
|
||||||
(into ids all-children))
|
(into ids all-children))
|
||||||
[])
|
[])
|
||||||
|
|
||||||
changes (-> (pcb/empty-changes it (:id page))
|
changes (-> changes
|
||||||
(pcb/with-page page)
|
(pcb/set-page-option :guides guides))
|
||||||
(pcb/with-objects objects)
|
|
||||||
(pcb/with-library-data file)
|
|
||||||
(pcb/set-page-option :guides guides))
|
|
||||||
|
|
||||||
changes (reduce (fn [changes component-id]
|
changes (reduce (fn [changes component-id]
|
||||||
;; It's important to delete the component before the main instance, because we
|
;; It's important to delete the component before the main instance, because we
|
||||||
;; need to store the instance position if we want to restore it later.
|
;; need to store the instance position if we want to restore it later.
|
||||||
(pcb/delete-component changes component-id))
|
(pcb/delete-component changes component-id))
|
||||||
changes
|
changes
|
||||||
components-to-delete)
|
components-to-delete)
|
||||||
|
|
||||||
changes (-> changes
|
changes (-> changes
|
||||||
(pcb/remove-objects all-children {:ignore-touched true})
|
(pcb/remove-objects all-children {:ignore-touched true})
|
||||||
(pcb/remove-objects ids)
|
(pcb/remove-objects ids)
|
||||||
(pcb/remove-objects empty-parents)
|
(pcb/remove-objects empty-parents)
|
||||||
(pcb/resize-parents all-parents)
|
(pcb/resize-parents all-parents)
|
||||||
(pcb/update-shapes groups-to-unmask
|
(pcb/update-shapes groups-to-unmask
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
(assoc shape :masked-group? false)))
|
(assoc shape :masked-group? false)))
|
||||||
(pcb/update-shapes (map :id interacting-shapes)
|
(pcb/update-shapes (map :id interacting-shapes)
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
(d/update-when shape :interactions
|
(d/update-when shape :interactions
|
||||||
(fn [interactions]
|
(fn [interactions]
|
||||||
(into []
|
(into []
|
||||||
(remove #(and (ctsi/has-destination %)
|
(remove #(and (ctsi/has-destination %)
|
||||||
(contains? ids (:destination %))))
|
(contains? ids (:destination %))))
|
||||||
interactions)))))
|
interactions)))))
|
||||||
(cond-> (seq starting-flows)
|
(cond-> (seq starting-flows)
|
||||||
(pcb/update-page-option :flows (fn [flows]
|
(pcb/update-page-option :flows (fn [flows]
|
||||||
(->> (map :id starting-flows)
|
(->> (map :id starting-flows)
|
||||||
(reduce ctp/remove-flow flows))))))
|
(reduce ctp/remove-flow flows))))))]
|
||||||
undo-id (js/Symbol)]
|
[changes all-parents])))
|
||||||
|
|
||||||
|
|
||||||
|
(defn delete-shapes-changes
|
||||||
|
[changes file page objects ids it components-v2]
|
||||||
|
(let [[changes _all-parents] (real-delete-shapes-changes changes file page objects ids it components-v2)]
|
||||||
|
changes))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- real-delete-shapes
|
||||||
|
[file page objects ids it components-v2]
|
||||||
|
(let [[changes all-parents] (real-delete-shapes-changes file page objects ids it components-v2)
|
||||||
|
undo-id (js/Symbol)]
|
||||||
(rx/of (dwu/start-undo-transaction undo-id)
|
(rx/of (dwu/start-undo-transaction undo-id)
|
||||||
(dc/detach-comment-thread ids)
|
(dc/detach-comment-thread ids)
|
||||||
(dch/commit-changes changes)
|
(dch/commit-changes changes)
|
||||||
|
@ -467,7 +481,7 @@
|
||||||
;; We have change only the hidden behaviour, to hide only the
|
;; We have change only the hidden behaviour, to hide only the
|
||||||
;; selected shape, block behaviour remains the same.
|
;; selected shape, block behaviour remains the same.
|
||||||
ids (if (boolean? blocked)
|
ids (if (boolean? blocked)
|
||||||
(into ids (->> ids (mapcat #(cph/get-children-ids objects %))))
|
(into ids (->> ids (mapcat #(cph/get-children-ids objects %))))
|
||||||
ids)]
|
ids)]
|
||||||
(rx/of (dch/update-shapes ids update-fn))))))
|
(rx/of (dch/update-shapes ids update-fn))))))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue