0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-24 07:46:13 -05:00

♻️ Refactor duplicate objects

This commit is contained in:
Andrés Moya 2022-02-17 13:44:56 +01:00
parent 9c895cb8bb
commit c626b1d106
4 changed files with 136 additions and 135 deletions

View file

@ -32,18 +32,23 @@
(vary-meta changes assoc ::objects objects))
(defn add-obj
([changes obj index]
(add-obj changes (assoc obj ::index index)))
([changes obj]
(let [add-change
{:type :add-obj
:id (:id obj)
:page-id (::page-id (meta changes))
:parent-id (:parent-id obj)
:frame-id (:frame-id obj)
:index (::index obj)
:obj (dissoc obj ::index :parent-id)}
(add-obj changes obj nil))
([changes obj {:keys [index ignore-touched] :or {index ::undefined ignore-touched false}}]
(let [obj (cond-> obj
(not= index ::undefined)
(assoc :index index))
add-change
{:type :add-obj
:id (:id obj)
:page-id (::page-id (meta changes))
:parent-id (:parent-id obj)
:frame-id (:frame-id obj)
:index (::index obj)
:ignore-touched ignore-touched
:obj (dissoc obj ::index :parent-id)}
del-change
{:type :del-obj
@ -201,3 +206,22 @@
:page-id page-id
:option option-key
:value old-val}))))
(defn reg-objects
[chdata shape-ids]
(let [page-id (::page-id (meta chdata))]
(-> chdata
(update :redo-changes conj {:type :reg-objects :page-id page-id :shapes shape-ids}))))
;; No need to do anything to undo
(defn amend-last-change
"Modify the last redo-changes added with an update function."
[chdata f]
(update chdata :redo-changes
#(conj (pop %) (f (peek %)))))
(defn amend-changes
"Modify all redo-changes with an update function."
[chdata f]
(update chdata :redo-changes #(mapv f %)))

View file

@ -1718,7 +1718,8 @@
;; Analyze the rchange and replace staled media and
;; references to the new uploaded media-objects.
(process-rchange [media-idx item]
(if (= :image (get-in item [:obj :type]))
(if (and (= (:type item) :add-obj)
(= :image (get-in item [:obj :type])))
(update-in item [:obj :metadata]
(fn [{:keys [id] :as mdata}]
(if-let [mobj (get media-idx id)]
@ -1818,51 +1819,44 @@
;; Calculate position for the pasted elements
[frame-id parent-id delta index] (calculate-paste-position state mouse-pos in-viewport?)
paste-objects (->> paste-objects
(d/mapm (fn [_ shape]
(-> shape
(assoc :frame-id frame-id)
(assoc :parent-id parent-id)
paste-objects (->> paste-objects
(d/mapm (fn [_ shape]
(-> shape
(assoc :frame-id frame-id)
(assoc :parent-id parent-id)
(cond->
;; if foreign instance, detach the shape
(foreign-instance? shape paste-objects state)
(dissoc :component-id
:component-file
:component-root?
:remote-synced?
:shape-ref
:touched))))))
(cond->
;; if foreign instance, detach the shape
(foreign-instance? shape paste-objects state)
(dissoc :component-id
:component-file
:component-root?
:remote-synced?
:shape-ref
:touched))))))
all-objects (merge page-objects paste-objects)
all-objects (merge page-objects paste-objects)
page-id (:current-page-id state)
unames (-> (wsh/lookup-page-objects state page-id)
(dwc/retrieve-used-names)) ;; TODO: move this calculation inside prepare-duplicate-changes?
rchanges (->> (dws/prepare-duplicate-changes all-objects page-id unames selected delta)
(mapv (partial process-rchange media-idx))
(mapv (partial change-add-obj-index paste-objects selected index)))
uchanges (mapv #(array-map :type :del-obj :page-id page-id :id (:id %))
(reverse rchanges))
page-id (:current-page-id state)
changes (-> (dws/prepare-duplicate-changes page-objects all-objects page-id selected delta it)
(pcb/amend-changes (partial process-rchange media-idx))
(pcb/amend-changes (partial change-add-obj-index paste-objects selected index)))
;; Adds a reg-objects operation so the groups are updated. We add all the new objects
new-objects-ids (->> rchanges (filter #(= (:type %) :add-obj)) (mapv :id))
new-objects-ids (->> changes :redo-changes (filter #(= (:type %) :add-obj)) (mapv :id))
rchanges (conj rchanges {:type :reg-objects
:page-id page-id
:shapes new-objects-ids})
changes (pcb/reg-objects changes new-objects-ids)
selected (->> rchanges
selected (->> changes
:redo-changes
(filter #(= (:type %) :add-obj))
(filter #(selected (:old-id %)))
(map #(get-in % [:obj :id]))
(into (d/ordered-set)))]
(rx/of (dch/commit-changes {:redo-changes rchanges
:undo-changes uchanges
:origin it})
(rx/of (dch/commit-changes changes)
(dwc/select-shapes selected))))]
(ptk/reify ::paste-shape
ptk/WatchEvent
(watch [it state _]

View file

@ -99,7 +99,7 @@
shape-id (:id boolean-data)
changes (-> (cb/empty-changes it page-id)
(cb/with-objects objects)
(cb/add-obj boolean-data index)
(cb/add-obj boolean-data {:index index})
(cb/change-parent shape-id shapes))]
(rx/of (dch/commit-changes changes)
(dwc/select-shapes (d/ordered-set shape-id)))))))))

View file

@ -10,6 +10,7 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as geom]
[app.common.math :as mth]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.spec.interactions :as cti]
@ -265,41 +266,32 @@
(declare prepare-duplicate-shape-change)
(defn prepare-duplicate-changes
"Prepare objects to paste: generate new id, give them unique names,
move to the position of mouse pointer, and find in what frame they
fit."
[objects page-id unames ids delta]
(let [unames (volatile! unames)
"Prepare objects to duplicate: generate new id, give them unique names,
move to the desired position, and recalculate parents and frames as needed."
[page-objects all-objects page-id ids delta it]
(let [unames (volatile! (dwc/retrieve-used-names page-objects))
update-unames! (fn [new-name] (vswap! unames conj new-name))
all-ids (reduce #(into %1 (cons %2 (cph/get-children-ids objects %2))) #{} ids)
all-ids (reduce #(into %1 (cons %2 (cph/get-children-ids all-objects %2))) #{} ids)
ids-map (into {} (map #(vector % (uuid/next))) all-ids)]
(loop [ids (seq ids)
chgs []]
(if ids
(let [id (first ids)
result (prepare-duplicate-change objects page-id unames update-unames! ids-map id delta)
result (if (vector? result) result [result])]
(recur
(next ids)
(into chgs result)))
chgs))))
(reduce (fn [changes id]
(prepare-duplicate-change changes all-objects page-id unames update-unames! ids-map id delta))
(-> (pcb/empty-changes it page-id)
(pcb/with-objects all-objects))
ids)))
(defn- prepare-duplicate-change
[objects page-id unames update-unames! ids-map id delta]
[changes objects page-id unames update-unames! ids-map id delta]
(let [obj (get objects id)]
(if (cph/frame-shape? obj)
(prepare-duplicate-frame-change objects page-id unames update-unames! ids-map obj delta)
(prepare-duplicate-shape-change objects page-id unames update-unames! ids-map obj delta (:frame-id obj) (:parent-id obj)))))
(prepare-duplicate-frame-change changes objects page-id unames update-unames! ids-map obj delta)
(prepare-duplicate-shape-change changes objects page-id unames update-unames! ids-map obj delta (:frame-id obj) (:parent-id obj)))))
(defn- prepare-duplicate-frame-change
[objects page-id unames update-unames! ids-map obj delta]
[changes objects page-id unames update-unames! ids-map obj delta]
(let [new-id (ids-map (:id obj))
frame-name (dwc/generate-unique-name @unames (:name obj))
_ (update-unames! frame-name)
sch (->> (map #(get objects %) (:shapes obj))
(mapcat #(prepare-duplicate-shape-change objects page-id unames update-unames! ids-map % delta new-id new-id)))
new-frame (-> obj
(assoc :id new-id
:name frame-name
@ -308,18 +300,27 @@
(geom/move delta)
(d/update-when :interactions #(cti/remap-interactions % ids-map objects)))
fch {:type :add-obj
:old-id (:id obj)
:page-id page-id
:id new-id
:frame-id uuid/zero
:obj new-frame}]
changes (-> (pcb/add-obj changes new-frame)
(pcb/amend-last-change #(assoc % :old-id (:id obj))))
(into [fch] sch)))
changes (reduce (fn [changes child]
(prepare-duplicate-shape-change changes
objects
page-id
unames
update-unames!
ids-map
child
delta
new-id
new-id))
changes
(map (d/getf objects) (:shapes obj)))]
changes))
(defn- prepare-duplicate-shape-change
[objects page-id unames update-unames! ids-map obj delta frame-id parent-id]
(when (some? obj)
[changes objects page-id unames update-unames! ids-map obj delta frame-id parent-id]
(if (some? obj)
(let [new-id (ids-map (:id obj))
parent-id (or parent-id frame-id)
name (dwc/generate-unique-name @unames (:name obj))
@ -328,55 +329,44 @@
new-obj (-> obj
(assoc :id new-id
:name name
:parent-id parent-id
:frame-id frame-id)
(dissoc :shapes)
(geom/move delta)
(d/update-when :interactions #(cti/remap-interactions % ids-map objects)))
children-changes
(loop [result []
cid (first (:shapes obj))
cids (rest (:shapes obj))]
(if (nil? cid)
result
(let [obj (get objects cid)
changes (prepare-duplicate-shape-change objects page-id unames update-unames! ids-map obj delta frame-id new-id)]
(recur
(into result changes)
(first cids)
(rest cids)))))]
changes (pcb/add-obj changes new-obj {:ignore-touched true})
changes (-> (pcb/add-obj changes new-obj {:ignore-touched true})
(pcb/amend-last-change #(assoc % :old-id (:id obj))))
(into [{:type :add-obj
:id new-id
:page-id page-id
:old-id (:id obj)
:frame-id frame-id
:parent-id parent-id
:ignore-touched true
:obj new-obj}]
children-changes))))
(declare update-indices)
changes (reduce (fn [changes child]
(prepare-duplicate-shape-change changes
objects
page-id
unames
update-unames!
ids-map
child
delta
frame-id
new-id))
changes
(map (d/getf objects) (:shapes obj)))]
changes)))
(defn duplicate-changes-update-indices
"Parses the change set when duplicating to set-up the appropriate indices"
"Updates the changes to correctly set the indexes of the duplicated objects,
depending on the index of the original object respect their parent."
[objects ids changes]
(let [;; index-map is a map that goes from parent-id => vector([id index-in-parent])
index-map (reduce (fn [index-map id]
(let [parent-id (get-in objects [id :parent-id])
parent-index (cph/get-position-on-parent objects id)]
(update index-map parent-id (fnil conj []) [id parent-index])))
{}
ids)
(let [process-id
(fn [index-map id]
(let [parent-id (get-in objects [id :parent-id])
parent-index (cph/get-position-on-parent objects id)]
(update index-map parent-id (fnil conj []) [id parent-index])))
index-map (reduce process-id {} ids)]
(-> changes (update-indices index-map))))
(defn update-indices
"Fixes the indices for a set of changes after a duplication. We need to
fix the indices to take into the account the movement of indices.
index-map is a map that goes from parent-id => vector([id index-in-parent])"
[changes index-map]
(let [inc-indices
inc-indices
(fn [[offset result] [id index]]
[(inc offset) (conj result [id (+ index offset)])])
@ -388,14 +378,12 @@
(second)
(into {})))
objects-indices (->> index-map (d/mapm fix-indices) (vals) (reduce merge))
objects-indices (->> index-map (d/mapm fix-indices) (vals) (reduce merge))]
update-change
(fn [change]
(let [index (get objects-indices (:old-id change))]
(-> change
(assoc :index index))))]
(mapv update-change changes)))
(pcb/amend-changes
changes
(fn [change]
(assoc change :index (get objects-indices (:old-id change)))))))
(defn clear-memorize-duplicated
[]
@ -457,17 +445,14 @@
(calc-duplicate-delta obj state objects))
(gpt/point 0 0))
unames (dwc/retrieve-used-names objects)
rchanges (->> (prepare-duplicate-changes objects page-id unames selected delta)
(duplicate-changes-update-indices objects selected))
uchanges (mapv #(array-map :type :del-obj :page-id page-id :id (:id %))
(reverse rchanges))
changes (->> (prepare-duplicate-changes objects objects page-id selected delta it)
(duplicate-changes-update-indices objects selected))
id-original (when (= (count selected) 1) (first selected))
selected (->> rchanges
selected (->> changes
:redo-changes
(filter #(= (:type %) :add-obj))
(filter #(selected (:old-id %)))
(map #(get-in % [:obj :id]))
(into (d/ordered-set)))
@ -475,9 +460,7 @@
id-duplicated (when (= (count selected) 1) (first selected))]
(rx/of (select-shapes selected)
(dch/commit-changes {:redo-changes rchanges
:undo-changes uchanges
:origin it})
(dch/commit-changes changes)
(memorize-duplicated id-original id-duplicated)))))))
(defn change-hover-state