0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-05 03:21:26 -05:00

Merge pull request #1607 from penpot/duplicate-flow

Duplicate flow
This commit is contained in:
Andrey Antukh 2022-02-22 08:48:20 +01:00 committed by GitHub
commit 486d89c5d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 208 additions and 178 deletions

View file

@ -5,6 +5,9 @@
### :boom: Breaking changes
### :sparkles: New features
### :bug: Bugs fixed
- Duplicate artboards create new flows if needed [Taiga #2221](https://tree.taiga.io/project/penpot/issue/2221)
### :arrow_up: Deps updates
### :heart: Community contributions by (Thank you!)

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)]
@ -1812,57 +1813,49 @@
;; Proceed with the standard shape paste process.
(do-paste [it state mouse-pos media]
(let [page-objects (wsh/lookup-page-objects state)
media-idx (d/index-by :prev-id media)
(let [page (wsh/lookup-page state)
media-idx (d/index-by :prev-id media)
;; 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 (:objects page) 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))
changes (-> (dws/prepare-duplicate-changes all-objects page 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,9 +10,11 @@
[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]
[app.common.spec.page :as ctp]
[app.common.uuid :as uuid]
[app.main.data.modal :as md]
[app.main.data.workspace.changes :as dch]
@ -263,14 +265,133 @@
(declare prepare-duplicate-change)
(declare prepare-duplicate-frame-change)
(declare prepare-duplicate-shape-change)
(declare prepare-duplicate-flows)
(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.
(defn prepare-duplicate-changes
"Prepare objects to duplicate: generate new id, give them unique names,
move to the desired position, and recalculate parents and frames as needed."
[all-objects page ids delta it]
(let [shapes (map (d/getf all-objects) ids)
unames (volatile! (dwc/retrieve-used-names (:objects page)))
update-unames! (fn [new-name] (vswap! unames conj new-name))
all-ids (reduce #(into %1 (cons %2 (cph/get-children-ids all-objects %2))) #{} ids)
ids-map (into {} (map #(vector % (uuid/next))) all-ids)]
(-> (reduce (fn [changes shape]
(prepare-duplicate-change changes all-objects page unames update-unames! ids-map shape delta))
(-> (pcb/empty-changes it)
(pcb/with-page page)
(pcb/with-objects all-objects))
shapes)
(prepare-duplicate-flows shapes page ids-map))))
index-map is a map that goes from parent-id => vector([id index-in-parent])"
[changes index-map]
(let [inc-indices
(defn- prepare-duplicate-change
[changes objects page unames update-unames! ids-map shape delta]
(if (cph/frame-shape? shape)
(prepare-duplicate-frame-change changes objects page unames update-unames! ids-map shape delta)
(prepare-duplicate-shape-change changes objects page unames update-unames! ids-map shape delta (:frame-id shape) (:parent-id shape))))
(defn- prepare-duplicate-frame-change
[changes objects page 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)
new-frame (-> obj
(assoc :id new-id
:name frame-name
:frame-id uuid/zero
:shapes [])
(geom/move delta)
(d/update-when :interactions #(cti/remap-interactions % ids-map objects)))
changes (-> (pcb/add-obj changes new-frame)
(pcb/amend-last-change #(assoc % :old-id (:id obj))))
changes (reduce (fn [changes child]
(prepare-duplicate-shape-change changes
objects
page
unames
update-unames!
ids-map
child
delta
new-id
new-id))
changes
(map (d/getf objects) (:shapes obj)))]
changes))
(defn- prepare-duplicate-shape-change
[changes objects page 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))
_ (update-unames! name)
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)))
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))))]
(reduce (fn [changes child]
(prepare-duplicate-shape-change changes
objects
page
unames
update-unames!
ids-map
child
delta
frame-id
new-id))
changes
(map (d/getf objects) (:shapes obj))))
changes))
(defn- prepare-duplicate-flows
[changes shapes page ids-map]
(let [flows (-> page :options :flows)
unames (volatile! (into #{} (map :name flows)))
frames-with-flow (->> shapes
(filter #(= (:type %) :frame))
(filter #(some? (ctp/get-frame-flow flows (:id %)))))]
(if-not (empty? frames-with-flow)
(let [new-flows (reduce
(fn [flows frame]
(let [name (dwc/generate-unique-name @unames "Flow-1")
_ (vswap! unames conj name)
new-flow {:id (uuid/next)
:name name
:starting-frame (get ids-map (:id frame))}]
(ctp/add-flow flows new-flow)))
flows
frames-with-flow)]
(pcb/set-page-option changes :flows new-flows))
changes)))
(defn duplicate-changes-update-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)
inc-indices
(fn [[offset result] [id index]]
[(inc offset) (conj result [id (+ index offset)])])
@ -282,118 +403,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)))
(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)
update-unames! (fn [new-name] (vswap! unames conj new-name))
all-ids (reduce #(into %1 (cons %2 (cph/get-children-ids 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))))
(defn duplicate-changes-update-indices
"Parses the change set when duplicating to set-up the appropriate indices"
[objects ids changes]
(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- prepare-duplicate-change
[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)))))
(defn- prepare-duplicate-shape-change
[objects page-id unames update-unames! ids-map obj delta frame-id parent-id]
(when (some? obj)
(let [new-id (ids-map (:id obj))
parent-id (or parent-id frame-id)
name (dwc/generate-unique-name @unames (:name obj))
_ (update-unames! name)
new-obj (-> obj
(assoc :id new-id
:name name
: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)))))]
(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))))
(defn- prepare-duplicate-frame-change
[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
:frame-id uuid/zero
:shapes [])
(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}]
(into [fch] sch)))
(pcb/amend-changes
changes
(fn [change]
(assoc change :index (get objects-indices (:old-id change)))))))
(defn clear-memorize-duplicated
[]
@ -447,25 +462,22 @@
ptk/WatchEvent
(watch [it state _]
(when (or (not move-delta?) (nil? (get-in state [:workspace-local :transform])))
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
(let [page (wsh/lookup-page state)
objects (:objects page)
selected (wsh/lookup-selected state)
delta (if (and move-delta? (= (count selected) 1))
(let [obj (get objects (first selected))]
(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 page 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)))
@ -473,9 +485,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