mirror of
https://github.com/penpot/penpot.git
synced 2025-01-23 06:58:58 -05:00
Merge pull request #2573 from penpot/hiru-delete-copy-elements
🎉 When deleting a shape inside a component copy, just hide it
This commit is contained in:
commit
5400fdb293
3 changed files with 240 additions and 182 deletions
|
@ -23,7 +23,13 @@
|
|||
(or (= (:shape-ref shape-inst) (:id shape-main))
|
||||
(= (:shape-ref shape-inst) (:shape-ref shape-main)))))
|
||||
|
||||
(defn main-instance?
|
||||
"Check if this shape is the root of the main instance of some component."
|
||||
[shape]
|
||||
(some? (:main-instance? shape)))
|
||||
|
||||
(defn is-main-instance?
|
||||
"Check if this shape is the root of the main instance of the given component."
|
||||
[shape-id page-id component]
|
||||
(and (= shape-id (:main-instance-id component))
|
||||
(= page-id (:main-instance-page component))))
|
||||
|
@ -38,3 +44,8 @@
|
|||
(and (some? (:component-id shape))
|
||||
(= (:component-file shape) library-id)))
|
||||
|
||||
(defn in-component-instance?
|
||||
"Check if the shape is inside a component instance."
|
||||
[shape]
|
||||
(some? (:shape-ref shape)))
|
||||
|
||||
|
|
|
@ -842,63 +842,6 @@
|
|||
(rx/of (dch/update-shapes selected #(assoc % :proportion-lock true)))
|
||||
(rx/of (dch/update-shapes selected #(update % :proportion-lock not))))))))
|
||||
|
||||
;; --- Update Shape Flags
|
||||
|
||||
(defn update-shape-flags
|
||||
[ids {:keys [blocked hidden] :as flags}]
|
||||
(us/verify (s/coll-of ::us/uuid) ids)
|
||||
(us/assert ::shape-attrs flags)
|
||||
(ptk/reify ::update-shape-flags
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [update-fn
|
||||
(fn [obj]
|
||||
(cond-> obj
|
||||
(boolean? blocked) (assoc :blocked blocked)
|
||||
(boolean? hidden) (assoc :hidden hidden)))
|
||||
objects (wsh/lookup-page-objects state)
|
||||
ids (into ids (->> ids (mapcat #(cph/get-children-ids objects %))))]
|
||||
(rx/of (dch/update-shapes ids update-fn))))))
|
||||
|
||||
(defn toggle-visibility-selected
|
||||
[]
|
||||
(ptk/reify ::toggle-visibility-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [selected (wsh/lookup-selected state)]
|
||||
(rx/of (dch/update-shapes selected #(update % :hidden not)))))))
|
||||
|
||||
(defn toggle-lock-selected
|
||||
[]
|
||||
(ptk/reify ::toggle-lock-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [selected (wsh/lookup-selected state)]
|
||||
(rx/of (dch/update-shapes selected #(update % :blocked not)))))))
|
||||
|
||||
(defn toggle-file-thumbnail-selected
|
||||
[]
|
||||
(ptk/reify ::toggle-file-thumbnail-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [selected (wsh/lookup-selected state)
|
||||
pages (-> state :workspace-data :pages-index vals)
|
||||
get-frames (fn [{:keys [objects id] :as page}]
|
||||
(->> (ctst/get-frames objects)
|
||||
(sequence
|
||||
(comp (filter :use-for-thumbnail?)
|
||||
(map :id)
|
||||
(remove selected)
|
||||
(map (partial vector id))))))]
|
||||
|
||||
(rx/concat
|
||||
(rx/from
|
||||
(->> (mapcat get-frames pages)
|
||||
(d/group-by first second)
|
||||
(map (fn [[page-id frame-ids]]
|
||||
(dch/update-shapes frame-ids #(dissoc % :use-for-thumbnail?) {:page-id page-id})))))
|
||||
(rx/of (dch/update-shapes selected #(update % :use-for-thumbnail? not))))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Navigation
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1755,6 +1698,12 @@
|
|||
(dm/export dwh/highlight-shape)
|
||||
(dm/export dwh/dehighlight-shape)
|
||||
|
||||
;; Shape flags
|
||||
(dm/export dwsh/update-shape-flags)
|
||||
(dm/export dwsh/toggle-visibility-selected)
|
||||
(dm/export dwsh/toggle-lock-selected)
|
||||
(dm/export dwsh/toggle-file-thumbnail-selected)
|
||||
|
||||
;; Groups
|
||||
(dm/export dwg/mask-group)
|
||||
(dm/export dwg/unmask-group)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[app.common.pages.changes-builder :as pcb]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
|
@ -133,6 +134,9 @@
|
|||
(rx/of (dch/commit-changes changes))
|
||||
(rx/empty))))))
|
||||
|
||||
(declare real-delete-shapes)
|
||||
(declare update-shape-flags)
|
||||
|
||||
(defn delete-shapes
|
||||
([ids] (delete-shapes nil ids))
|
||||
([page-id ids]
|
||||
|
@ -140,141 +144,175 @@
|
|||
(ptk/reify ::delete-shapes
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [file-id (:current-file-id state)
|
||||
page-id (or page-id (:current-page-id state))
|
||||
file (wsh/get-file state file-id)
|
||||
page (wsh/lookup-page state page-id)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
|
||||
ids (cph/clean-loops objects ids)
|
||||
lookup (d/getf objects)
|
||||
|
||||
layout-ids (->> ids
|
||||
(mapcat (partial cph/get-parent-ids objects))
|
||||
(filter (partial ctl/layout? objects)))
|
||||
(let [file-id (:current-file-id state)
|
||||
page-id (or page-id (:current-page-id state))
|
||||
file (wsh/get-file state file-id)
|
||||
page (wsh/lookup-page state page-id)
|
||||
objects (wsh/lookup-page-objects state page-id)
|
||||
|
||||
components-v2 (features/active-feature? state :components-v2)
|
||||
|
||||
groups-to-unmask
|
||||
(reduce (fn [group-ids id]
|
||||
;; When the shape to delete is the mask of a masked group,
|
||||
;; the mask condition must be removed, and it must be
|
||||
;; converted to a normal group.
|
||||
(let [obj (lookup id)
|
||||
parent (lookup (:parent-id obj))]
|
||||
(if (and (:masked-group? parent)
|
||||
(= id (first (:shapes parent))))
|
||||
(conj group-ids (:id parent))
|
||||
group-ids)))
|
||||
#{}
|
||||
ids)
|
||||
ids (cph/clean-loops objects ids)
|
||||
|
||||
interacting-shapes
|
||||
(filter (fn [shape]
|
||||
;; If any of the deleted shapes is the destination of
|
||||
;; some interaction, this must be deleted, too.
|
||||
(let [interactions (:interactions shape)]
|
||||
(some #(and (ctsi/has-destination %)
|
||||
(contains? ids (:destination %)))
|
||||
interactions)))
|
||||
(vals objects))
|
||||
in-component-copy?
|
||||
(fn [shape-id]
|
||||
;; Look for shapes that are inside a component copy, but are
|
||||
;; not the root. In this case, they must not be deleted,
|
||||
;; but hidden (to be able to recover them more easily).
|
||||
(let [shape (get objects shape-id)
|
||||
component-shape (cph/get-component-shape objects shape)]
|
||||
(and (ctk/in-component-instance? shape)
|
||||
(not= shape component-shape)
|
||||
(not (ctk/main-instance? component-shape)))))
|
||||
|
||||
;; If any of the deleted shapes is a frame with guides
|
||||
guides (into {}
|
||||
(comp (map second)
|
||||
(remove #(contains? ids (:frame-id %)))
|
||||
(map (juxt :id identity)))
|
||||
(dm/get-in page [:options :guides]))
|
||||
|
||||
starting-flows
|
||||
(filter (fn [flow]
|
||||
;; If any of the deleted is a frame that starts a flow,
|
||||
;; this must be deleted, too.
|
||||
(contains? ids (:starting-frame flow)))
|
||||
(-> page :options :flows))
|
||||
|
||||
all-parents
|
||||
(reduce (fn [res id]
|
||||
;; All parents of any deleted shape must be resized.
|
||||
(into res (cph/get-parent-ids objects id)))
|
||||
(d/ordered-set)
|
||||
ids)
|
||||
|
||||
all-children
|
||||
(->> ids ;; Children of deleted shapes must be also deleted.
|
||||
(reduce (fn [res id]
|
||||
(into res (cph/get-children-ids objects id)))
|
||||
[])
|
||||
(reverse)
|
||||
(into (d/ordered-set)))
|
||||
|
||||
find-all-empty-parents
|
||||
(fn recursive-find-empty-parents [empty-parents]
|
||||
(let [all-ids (into empty-parents ids)
|
||||
contains? (partial contains? all-ids)
|
||||
xform (comp (map lookup)
|
||||
(filter cph/group-shape?)
|
||||
(remove #(->> (:shapes %) (remove contains?) seq))
|
||||
(map :id))
|
||||
parents (into #{} xform all-parents)]
|
||||
(if (= empty-parents parents)
|
||||
empty-parents
|
||||
(recursive-find-empty-parents parents))))
|
||||
|
||||
empty-parents
|
||||
;; Any parent whose children are all deleted, must be deleted too.
|
||||
(into (d/ordered-set) (find-all-empty-parents #{}))
|
||||
|
||||
components-to-delete
|
||||
[ids-to-delete ids-to-hide]
|
||||
(if components-v2
|
||||
(reduce (fn [components id]
|
||||
(let [shape (get objects id)]
|
||||
(if (and (= (:component-file shape) file-id) ;; Main instances should exist only in local file
|
||||
(:main-instance? shape)) ;; but check anyway
|
||||
(conj components (:component-id shape))
|
||||
components)))
|
||||
[]
|
||||
(into ids all-children))
|
||||
[])
|
||||
(loop [ids-seq (seq ids)
|
||||
ids-to-delete []
|
||||
ids-to-hide []]
|
||||
(let [id (first ids-seq)]
|
||||
(if (nil? id)
|
||||
[ids-to-delete ids-to-hide]
|
||||
(if (in-component-copy? id)
|
||||
(recur (rest ids-seq)
|
||||
ids-to-delete
|
||||
(conj ids-to-hide id))
|
||||
(recur (rest ids-seq)
|
||||
(conj ids-to-delete id)
|
||||
ids-to-hide)))))
|
||||
[ids []])]
|
||||
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-page page)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/with-library-data file)
|
||||
(pcb/set-page-option :guides guides))
|
||||
(rx/concat (rx/of (update-shape-flags ids-to-hide {:hidden true}))
|
||||
(real-delete-shapes file page objects ids-to-delete it components-v2)))))))
|
||||
|
||||
changes (reduce (fn [changes component-id]
|
||||
;; 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.
|
||||
(pcb/delete-component changes component-id components-v2))
|
||||
changes
|
||||
components-to-delete)
|
||||
(defn- real-delete-shapes
|
||||
[file page objects ids it components-v2]
|
||||
(let [lookup (d/getf objects)
|
||||
|
||||
changes (-> changes
|
||||
(pcb/remove-objects all-children)
|
||||
(pcb/remove-objects ids)
|
||||
(pcb/remove-objects empty-parents)
|
||||
(pcb/resize-parents all-parents)
|
||||
(pcb/update-shapes groups-to-unmask
|
||||
(fn [shape]
|
||||
(assoc shape :masked-group? false)))
|
||||
(pcb/update-shapes (map :id interacting-shapes)
|
||||
(fn [shape]
|
||||
(d/update-when shape :interactions
|
||||
(fn [interactions]
|
||||
(into []
|
||||
(remove #(and (ctsi/has-destination %)
|
||||
(contains? ids (:destination %))))
|
||||
interactions)))))
|
||||
(cond-> (seq starting-flows)
|
||||
(pcb/update-page-option :flows (fn [flows]
|
||||
(->> (map :id starting-flows)
|
||||
(reduce ctp/remove-flow flows))))))]
|
||||
layout-ids (->> ids
|
||||
(mapcat (partial cph/get-parent-ids objects))
|
||||
(filter (partial ctl/layout? objects)))
|
||||
|
||||
(rx/of (dc/detach-comment-thread ids)
|
||||
(dwsul/update-layout-positions all-parents)
|
||||
(dch/commit-changes changes)
|
||||
(dwsul/update-layout-positions layout-ids)))))))
|
||||
groups-to-unmask
|
||||
(reduce (fn [group-ids id]
|
||||
;; When the shape to delete is the mask of a masked group,
|
||||
;; the mask condition must be removed, and it must be
|
||||
;; converted to a normal group.
|
||||
(let [obj (lookup id)
|
||||
parent (lookup (:parent-id obj))]
|
||||
(if (and (:masked-group? parent)
|
||||
(= id (first (:shapes parent))))
|
||||
(conj group-ids (:id parent))
|
||||
group-ids)))
|
||||
#{}
|
||||
ids)
|
||||
|
||||
interacting-shapes
|
||||
(filter (fn [shape]
|
||||
;; If any of the deleted shapes is the destination of
|
||||
;; some interaction, this must be deleted, too.
|
||||
(let [interactions (:interactions shape)]
|
||||
(some #(and (ctsi/has-destination %)
|
||||
(contains? ids (:destination %)))
|
||||
interactions)))
|
||||
(vals objects))
|
||||
|
||||
;; If any of the deleted shapes is a frame with guides
|
||||
guides (into {}
|
||||
(comp (map second)
|
||||
(remove #(contains? ids (:frame-id %)))
|
||||
(map (juxt :id identity)))
|
||||
(dm/get-in page [:options :guides]))
|
||||
|
||||
starting-flows
|
||||
(filter (fn [flow]
|
||||
;; If any of the deleted is a frame that starts a flow,
|
||||
;; this must be deleted, too.
|
||||
(contains? ids (:starting-frame flow)))
|
||||
(-> page :options :flows))
|
||||
|
||||
all-parents
|
||||
(reduce (fn [res id]
|
||||
;; All parents of any deleted shape must be resized.
|
||||
(into res (cph/get-parent-ids objects id)))
|
||||
(d/ordered-set)
|
||||
ids)
|
||||
|
||||
all-children
|
||||
(->> ids ;; Children of deleted shapes must be also deleted.
|
||||
(reduce (fn [res id]
|
||||
(into res (cph/get-children-ids objects id)))
|
||||
[])
|
||||
(reverse)
|
||||
(into (d/ordered-set)))
|
||||
|
||||
find-all-empty-parents
|
||||
(fn recursive-find-empty-parents [empty-parents]
|
||||
(let [all-ids (into empty-parents ids)
|
||||
contains? (partial contains? all-ids)
|
||||
xform (comp (map lookup)
|
||||
(filter cph/group-shape?)
|
||||
(remove #(->> (:shapes %) (remove contains?) seq))
|
||||
(map :id))
|
||||
parents (into #{} xform all-parents)]
|
||||
(if (= empty-parents parents)
|
||||
empty-parents
|
||||
(recursive-find-empty-parents parents))))
|
||||
|
||||
empty-parents
|
||||
;; Any parent whose children are all deleted, must be deleted too.
|
||||
(into (d/ordered-set) (find-all-empty-parents #{}))
|
||||
|
||||
components-to-delete
|
||||
(if components-v2
|
||||
(reduce (fn [components id]
|
||||
(let [shape (get objects id)]
|
||||
(if (and (= (:component-file shape) (:id file)) ;; Main instances should exist only in local file
|
||||
(:main-instance? shape)) ;; but check anyway
|
||||
(conj components (:component-id shape))
|
||||
components)))
|
||||
[]
|
||||
(into ids all-children))
|
||||
[])
|
||||
|
||||
changes (-> (pcb/empty-changes it (:id page))
|
||||
(pcb/with-page page)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/with-library-data file)
|
||||
(pcb/set-page-option :guides guides))
|
||||
|
||||
changes (reduce (fn [changes component-id]
|
||||
;; 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.
|
||||
(pcb/delete-component changes component-id components-v2))
|
||||
changes
|
||||
components-to-delete)
|
||||
|
||||
changes (-> changes
|
||||
(pcb/remove-objects all-children)
|
||||
(pcb/remove-objects ids)
|
||||
(pcb/remove-objects empty-parents)
|
||||
(pcb/resize-parents all-parents)
|
||||
(pcb/update-shapes groups-to-unmask
|
||||
(fn [shape]
|
||||
(assoc shape :masked-group? false)))
|
||||
(pcb/update-shapes (map :id interacting-shapes)
|
||||
(fn [shape]
|
||||
(d/update-when shape :interactions
|
||||
(fn [interactions]
|
||||
(into []
|
||||
(remove #(and (ctsi/has-destination %)
|
||||
(contains? ids (:destination %))))
|
||||
interactions)))))
|
||||
(cond-> (seq starting-flows)
|
||||
(pcb/update-page-option :flows (fn [flows]
|
||||
(->> (map :id starting-flows)
|
||||
(reduce ctp/remove-flow flows))))))]
|
||||
|
||||
(rx/of (dc/detach-comment-thread ids)
|
||||
(dwsul/update-layout-positions all-parents)
|
||||
(dch/commit-changes changes)
|
||||
(dwsul/update-layout-positions layout-ids))))
|
||||
|
||||
(defn create-and-add-shape
|
||||
[type frame-x frame-y data]
|
||||
|
@ -330,3 +368,63 @@
|
|||
(add-shape shape)
|
||||
(move-shapes-into-frame (:id shape) selected)
|
||||
(dwu/commit-undo-transaction)))))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Shape Flags
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn update-shape-flags
|
||||
[ids {:keys [blocked hidden] :as flags}]
|
||||
(us/verify (s/coll-of ::us/uuid) ids)
|
||||
(us/assert ::shape-attrs flags)
|
||||
(ptk/reify ::update-shape-flags
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [update-fn
|
||||
(fn [obj]
|
||||
(cond-> obj
|
||||
(boolean? blocked) (assoc :blocked blocked)
|
||||
(boolean? hidden) (assoc :hidden hidden)))
|
||||
objects (wsh/lookup-page-objects state)
|
||||
ids (into ids (->> ids (mapcat #(cph/get-children-ids objects %))))]
|
||||
(rx/of (dch/update-shapes ids update-fn))))))
|
||||
|
||||
(defn toggle-visibility-selected
|
||||
[]
|
||||
(ptk/reify ::toggle-visibility-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [selected (wsh/lookup-selected state)]
|
||||
(rx/of (dch/update-shapes selected #(update % :hidden not)))))))
|
||||
|
||||
(defn toggle-lock-selected
|
||||
[]
|
||||
(ptk/reify ::toggle-lock-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [selected (wsh/lookup-selected state)]
|
||||
(rx/of (dch/update-shapes selected #(update % :blocked not)))))))
|
||||
|
||||
(defn toggle-file-thumbnail-selected
|
||||
[]
|
||||
(ptk/reify ::toggle-file-thumbnail-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [selected (wsh/lookup-selected state)
|
||||
pages (-> state :workspace-data :pages-index vals)
|
||||
get-frames (fn [{:keys [objects id] :as page}]
|
||||
(->> (ctst/get-frames objects)
|
||||
(sequence
|
||||
(comp (filter :use-for-thumbnail?)
|
||||
(map :id)
|
||||
(remove selected)
|
||||
(map (partial vector id))))))]
|
||||
|
||||
(rx/concat
|
||||
(rx/from
|
||||
(->> (mapcat get-frames pages)
|
||||
(d/group-by first second)
|
||||
(map (fn [[page-id frame-ids]]
|
||||
(dch/update-shapes frame-ids #(dissoc % :use-for-thumbnail?) {:page-id page-id})))))
|
||||
(rx/of (dch/update-shapes selected #(update % :use-for-thumbnail? not))))))))
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue