0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-08 07:50:43 -05:00

Enhance duplicating prototype connections

This commit is contained in:
Andrés Moya 2021-10-15 11:51:37 +02:00 committed by Andrey Antukh
parent a1908be982
commit 92f89c6cc1
8 changed files with 99 additions and 58 deletions

View file

@ -15,6 +15,7 @@
### :bug: Bugs fixed
- Enhance duplicating prototype connections behaviour [Taiga #2093](https://tree.taiga.io/project/penpot/us/2093).
- Fix color and typographies refs lost when duplicated file [Taiga #2165](https://tree.taiga.io/project/penpot/issue/2165).
- Fix problem with overflow dropdown on stroke-cap [#1216](https://github.com/penpot/penpot/issues/1216).
- Fix menu context for single element nested in components [#1186](https://github.com/penpot/penpot/issues/1186).

View file

@ -44,6 +44,7 @@
(d/export helpers/is-shape-grouped)
(d/export helpers/get-parent)
(d/export helpers/get-parents)
(d/export helpers/get-frame)
(d/export helpers/clean-loops)
(d/export helpers/calculate-invalid-targets)
(d/export helpers/valid-frame-target)
@ -67,6 +68,7 @@
(d/export helpers/merge-path-item)
(d/export helpers/compact-path)
(d/export helpers/compact-name)
(d/export helpers/unframed-shape?)
;; Indices
(d/export indices/calculate-z-index)

View file

@ -484,3 +484,10 @@
(let [children (get-object-with-children frame-id objects)]
(or (some cti/flow-origin? (map :interactions children))
(some #(cti/flow-to? % frame-id) (map :interactions (vals objects))))))
(defn unframed-shape?
"Checks if it's a non-frame shape in the top level."
[shape]
(and (not= (:type shape) :frame)
(= (:frame-id shape) uuid/zero)))

View file

@ -6,6 +6,7 @@
(ns app.common.types.interactions
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.spec :as us]
[clojure.spec.alpha :as s]))
@ -340,6 +341,21 @@
[interactions index update-fn]
(update interactions index update-fn))
(defn remap-interactions
"Update all interactions whose destination points to a shape in the
map to the new id. And remove the ones whose destination does not exist
in the map nor in the objects tree."
[interactions ids-map objects]
(when (some? interactions)
(->> interactions
(filterv (fn [interaction]
(let [destination (:destination interaction)]
(or (nil? destination)
(contains? ids-map destination)
(contains? objects destination)))))
(mapv (fn [interaction]
(d/update-when interaction :destination #(get ids-map % %)))))))
(defn actionable?
"Check if there is any interaction that is clickable by the user"
[interactions]

View file

@ -1557,8 +1557,12 @@
(= :frame (get-in objects [(first selected) :type])))))
(defn- paste-shape
[{:keys [selected objects images] :as data} in-viewport?] ;; TODO: perhaps rename 'objects' to 'shapes', because it contains only
(letfn [;; Given a file-id and img (part generated by the ;; the shapes to paste, not the whole page tree of shapes
[{selected :selected
paste-objects :objects ;; rename this because here comes only the clipboard shapes,
images :images ;; not the whole page tree of shapes.
:as data}
in-viewport?]
(letfn [;; Given a file-id and img (part generated by the
;; copy-selected event), uploads the new media.
(upload-media [file-id imgpart]
(->> (http/send! {:uri (:file-data imgpart)
@ -1590,7 +1594,7 @@
(calculate-paste-position [state mouse-pos in-viewport?]
(let [page-objects (wsh/lookup-page-objects state)
selected-objs (map #(get objects %) selected)
selected-objs (map #(get paste-objects %) selected)
has-frame? (d/seek #(= (:type %) :frame) selected-objs)
page-selected (wsh/lookup-selected state)
wrapper (gsh/selection-rect selected-objs)
@ -1617,12 +1621,12 @@
[frame-id parent-id delta index]))))
;; Change the indexes if the paste is done with an element selected
(change-add-obj-index [objects selected index change]
(change-add-obj-index [paste-objects selected index change]
(let [set-index (fn [[result index] id]
[(assoc result id index) (inc index)])
map-ids (when index
(->> (vals objects)
(->> (vals paste-objects)
(filter #(not (selected (:parent-id %))))
(map :id)
(reduce set-index [{} (inc index)])
@ -1634,8 +1638,8 @@
;; Check if the shape is an instance whose master is defined in a
;; library that is not linked to the current file
(foreign-instance? [shape objects state]
(let [root (cph/get-root-shape shape objects)
(foreign-instance? [shape paste-objects state]
(let [root (cph/get-root-shape shape paste-objects)
root-file-id (:component-file root)]
(and (some? root)
(not= root-file-id (:current-file-id state))
@ -1643,34 +1647,36 @@
;; Procceed with the standard shape paste procediment.
(do-paste [it state mouse-pos media]
(let [media-idx (d/index-by :prev-id media)
(let [page-objects (wsh/lookup-page-objects state)
all-objects (merge page-objects paste-objects)
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?)
objects (->> 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 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))))))
page-id (:current-page-id state)
unames (-> (wsh/lookup-page-objects state page-id)
(dwc/retrieve-used-names)) ;; TODO: move this calculation inside prepare-duplcate-changes?
rchanges (->> (dws/prepare-duplicate-changes objects page-id unames selected delta)
rchanges (->> (dws/prepare-duplicate-changes all-objects page-id unames selected delta)
(mapv (partial process-rchange media-idx))
(mapv (partial change-add-obj-index objects selected index)))
(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))

View file

@ -12,6 +12,7 @@
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.spec :as us]
[app.common.types.interactions :as cti]
[app.common.uuid :as uuid]
[app.main.data.modal :as md]
[app.main.data.workspace.changes :as dch]
@ -288,12 +289,17 @@
fit."
[objects page-id unames ids delta]
(let [unames (volatile! unames)
update-unames! (fn [new-name] (vswap! unames conj new-name))]
update-unames! (fn [new-name] (vswap! unames conj new-name))
all-ids (reduce (fn [ids-set id]
(into ids-set (cons id (cp/get-children id objects))))
#{}
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! id delta)
result (prepare-duplicate-change objects page-id unames update-unames! ids-map id delta)
result (if (vector? result) result [result])]
(recur
(next ids)
@ -313,22 +319,27 @@
(-> changes (update-indices index-map))))
(defn- prepare-duplicate-change
[objects page-id unames update-unames! id delta]
[objects page-id unames update-unames! ids-map id delta]
(let [obj (get objects id)]
(if (= :frame (:type obj))
(prepare-duplicate-frame-change objects page-id unames update-unames! obj delta)
(prepare-duplicate-shape-change objects page-id unames update-unames! obj delta (:frame-id obj) (:parent-id 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! obj delta frame-id parent-id]
[objects page-id unames update-unames! ids-map obj delta frame-id parent-id]
(when (some? obj)
(let [id (uuid/next)
(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)
renamed-obj (assoc obj :id id :name name)
moved-obj (geom/move renamed-obj delta)
parent-id (or parent-id frame-id)
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 []
@ -337,47 +348,45 @@
(if (nil? cid)
result
(let [obj (get objects cid)
changes (prepare-duplicate-shape-change objects page-id unames update-unames! obj delta frame-id id)]
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)))))
(rest cids)))))]
reframed-obj (-> moved-obj
(assoc :frame-id frame-id)
(dissoc :shapes))]
(into [{:type :add-obj
:id id
:id new-id
:page-id page-id
:old-id (:id obj)
:frame-id frame-id
:parent-id parent-id
:ignore-touched true
:obj (dissoc reframed-obj :shapes)}]
:obj new-obj}]
children-changes))))
(defn- prepare-duplicate-frame-change
[objects page-id unames update-unames! obj delta]
(let [frame-id (uuid/next)
[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! % delta frame-id frame-id)))
(mapcat #(prepare-duplicate-shape-change objects page-id unames update-unames! ids-map % delta new-id new-id)))
frame (-> obj
(assoc :id frame-id)
(assoc :name frame-name)
(assoc :frame-id uuid/zero)
(assoc :shapes [])
(geom/move delta))
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 frame-id
:id new-id
:frame-id uuid/zero
:obj frame}]
:obj new-frame}]
(into [fch] sch)))

View file

@ -377,7 +377,7 @@
[:& page-flows {:flows flows}])
[:div.element-set.interactions-options
(when (and shape (not= (:frame-id shape) uuid/zero))
(when (and shape (not (cp/unframed-shape? shape)))
[:div.element-set-title
[:span (tr "workspace.options.interactions")]
[:div.add-page {:on-click add-interaction}
@ -385,7 +385,7 @@
[:div.element-set-content
(when (= (count interactions) 0)
[:*
(when (and shape (not= (:frame-id shape) uuid/zero))
(when (and shape (not (cp/unframed-shape? shape)))
[:*
[:div.interactions-help-icon i/plus]
[:div.interactions-help.separator (tr "workspace.options.add-interaction")]])

View file

@ -8,9 +8,8 @@
"Visually show shape interactions in workspace"
(:require
[app.common.data :as d]
[app.common.pages.helpers :as cph]
[app.common.pages :as cp]
[app.common.types.interactions :as cti]
[app.common.uuid :as uuid]
[app.main.data.workspace :as dw]
[app.main.refs :as refs]
[app.main.store :as st]
@ -210,7 +209,7 @@
(st/emit! (dw/start-move-overlay-pos index)))]
(when dest-shape
(let [orig-frame (cph/get-frame orig-shape objects)
(let [orig-frame (cp/get-frame orig-shape objects)
marker-x (+ (:x orig-frame) (:x position))
marker-y (+ (:y orig-frame) (:y position))
width (:width dest-shape)
@ -320,7 +319,8 @@
:position (:overlay-position interaction)
:objects objects
:hover-disabled? hover-disabled?}]))])))
(when (and (not= (:frame-id shape) uuid/zero)
(when (and shape
(not (cp/unframed-shape? shape))
(not (#{:move :rotate} current-transform)))
[:& interaction-handle {:key (:id shape)
:index nil