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:
parent
a1908be982
commit
92f89c6cc1
8 changed files with 99 additions and 58 deletions
|
@ -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).
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
|
|
@ -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")]])
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue