mirror of
https://github.com/penpot/penpot.git
synced 2025-05-25 12:06:11 +02: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
|
### :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 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 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).
|
- 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/is-shape-grouped)
|
||||||
(d/export helpers/get-parent)
|
(d/export helpers/get-parent)
|
||||||
(d/export helpers/get-parents)
|
(d/export helpers/get-parents)
|
||||||
|
(d/export helpers/get-frame)
|
||||||
(d/export helpers/clean-loops)
|
(d/export helpers/clean-loops)
|
||||||
(d/export helpers/calculate-invalid-targets)
|
(d/export helpers/calculate-invalid-targets)
|
||||||
(d/export helpers/valid-frame-target)
|
(d/export helpers/valid-frame-target)
|
||||||
|
@ -67,6 +68,7 @@
|
||||||
(d/export helpers/merge-path-item)
|
(d/export helpers/merge-path-item)
|
||||||
(d/export helpers/compact-path)
|
(d/export helpers/compact-path)
|
||||||
(d/export helpers/compact-name)
|
(d/export helpers/compact-name)
|
||||||
|
(d/export helpers/unframed-shape?)
|
||||||
|
|
||||||
;; Indices
|
;; Indices
|
||||||
(d/export indices/calculate-z-index)
|
(d/export indices/calculate-z-index)
|
||||||
|
|
|
@ -484,3 +484,10 @@
|
||||||
(let [children (get-object-with-children frame-id objects)]
|
(let [children (get-object-with-children frame-id objects)]
|
||||||
(or (some cti/flow-origin? (map :interactions children))
|
(or (some cti/flow-origin? (map :interactions children))
|
||||||
(some #(cti/flow-to? % frame-id) (map :interactions (vals objects))))))
|
(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
|
(ns app.common.types.interactions
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[clojure.spec.alpha :as s]))
|
[clojure.spec.alpha :as s]))
|
||||||
|
@ -340,6 +341,21 @@
|
||||||
[interactions index update-fn]
|
[interactions index update-fn]
|
||||||
(update 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?
|
(defn actionable?
|
||||||
"Check if there is any interaction that is clickable by the user"
|
"Check if there is any interaction that is clickable by the user"
|
||||||
[interactions]
|
[interactions]
|
||||||
|
|
|
@ -1557,8 +1557,12 @@
|
||||||
(= :frame (get-in objects [(first selected) :type])))))
|
(= :frame (get-in objects [(first selected) :type])))))
|
||||||
|
|
||||||
(defn- paste-shape
|
(defn- paste-shape
|
||||||
[{:keys [selected objects images] :as data} in-viewport?] ;; TODO: perhaps rename 'objects' to 'shapes', because it contains only
|
[{selected :selected
|
||||||
(letfn [;; Given a file-id and img (part generated by the ;; the shapes to paste, not the whole page tree of shapes
|
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.
|
;; copy-selected event), uploads the new media.
|
||||||
(upload-media [file-id imgpart]
|
(upload-media [file-id imgpart]
|
||||||
(->> (http/send! {:uri (:file-data imgpart)
|
(->> (http/send! {:uri (:file-data imgpart)
|
||||||
|
@ -1590,7 +1594,7 @@
|
||||||
|
|
||||||
(calculate-paste-position [state mouse-pos in-viewport?]
|
(calculate-paste-position [state mouse-pos in-viewport?]
|
||||||
(let [page-objects (wsh/lookup-page-objects state)
|
(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)
|
has-frame? (d/seek #(= (:type %) :frame) selected-objs)
|
||||||
page-selected (wsh/lookup-selected state)
|
page-selected (wsh/lookup-selected state)
|
||||||
wrapper (gsh/selection-rect selected-objs)
|
wrapper (gsh/selection-rect selected-objs)
|
||||||
|
@ -1617,12 +1621,12 @@
|
||||||
[frame-id parent-id delta index]))))
|
[frame-id parent-id delta index]))))
|
||||||
|
|
||||||
;; Change the indexes if the paste is done with an element selected
|
;; 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]
|
(let [set-index (fn [[result index] id]
|
||||||
[(assoc result id index) (inc index)])
|
[(assoc result id index) (inc index)])
|
||||||
|
|
||||||
map-ids (when index
|
map-ids (when index
|
||||||
(->> (vals objects)
|
(->> (vals paste-objects)
|
||||||
(filter #(not (selected (:parent-id %))))
|
(filter #(not (selected (:parent-id %))))
|
||||||
(map :id)
|
(map :id)
|
||||||
(reduce set-index [{} (inc index)])
|
(reduce set-index [{} (inc index)])
|
||||||
|
@ -1634,8 +1638,8 @@
|
||||||
|
|
||||||
;; Check if the shape is an instance whose master is defined in a
|
;; Check if the shape is an instance whose master is defined in a
|
||||||
;; library that is not linked to the current file
|
;; library that is not linked to the current file
|
||||||
(foreign-instance? [shape objects state]
|
(foreign-instance? [shape paste-objects state]
|
||||||
(let [root (cph/get-root-shape shape objects)
|
(let [root (cph/get-root-shape shape paste-objects)
|
||||||
root-file-id (:component-file root)]
|
root-file-id (:component-file root)]
|
||||||
(and (some? root)
|
(and (some? root)
|
||||||
(not= root-file-id (:current-file-id state))
|
(not= root-file-id (:current-file-id state))
|
||||||
|
@ -1643,34 +1647,36 @@
|
||||||
|
|
||||||
;; Procceed with the standard shape paste procediment.
|
;; Procceed with the standard shape paste procediment.
|
||||||
(do-paste [it state mouse-pos media]
|
(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
|
;; Calculate position for the pasted elements
|
||||||
[frame-id parent-id delta index] (calculate-paste-position state mouse-pos in-viewport?)
|
[frame-id parent-id delta index] (calculate-paste-position state mouse-pos in-viewport?)
|
||||||
|
|
||||||
objects (->> objects
|
paste-objects (->> paste-objects
|
||||||
(d/mapm (fn [_ shape]
|
(d/mapm (fn [_ shape]
|
||||||
(-> shape
|
(-> shape
|
||||||
(assoc :frame-id frame-id)
|
(assoc :frame-id frame-id)
|
||||||
(assoc :parent-id parent-id)
|
(assoc :parent-id parent-id)
|
||||||
|
|
||||||
(cond->
|
(cond->
|
||||||
;; if foreign instance, detach the shape
|
;; if foreign instance, detach the shape
|
||||||
(foreign-instance? shape objects state)
|
(foreign-instance? shape paste-objects state)
|
||||||
(dissoc :component-id
|
(dissoc :component-id
|
||||||
:component-file
|
:component-file
|
||||||
:component-root?
|
:component-root?
|
||||||
:remote-synced?
|
:remote-synced?
|
||||||
:shape-ref
|
:shape-ref
|
||||||
:touched))))))
|
:touched))))))
|
||||||
|
|
||||||
page-id (:current-page-id state)
|
page-id (:current-page-id state)
|
||||||
unames (-> (wsh/lookup-page-objects state page-id)
|
unames (-> (wsh/lookup-page-objects state page-id)
|
||||||
(dwc/retrieve-used-names)) ;; TODO: move this calculation inside prepare-duplcate-changes?
|
(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 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 %))
|
uchanges (mapv #(array-map :type :del-obj :page-id page-id :id (:id %))
|
||||||
(reverse rchanges))
|
(reverse rchanges))
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
|
[app.common.types.interactions :as cti]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.main.data.modal :as md]
|
[app.main.data.modal :as md]
|
||||||
[app.main.data.workspace.changes :as dch]
|
[app.main.data.workspace.changes :as dch]
|
||||||
|
@ -288,12 +289,17 @@
|
||||||
fit."
|
fit."
|
||||||
[objects page-id unames ids delta]
|
[objects page-id unames ids delta]
|
||||||
(let [unames (volatile! unames)
|
(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)
|
(loop [ids (seq ids)
|
||||||
chgs []]
|
chgs []]
|
||||||
(if ids
|
(if ids
|
||||||
(let [id (first 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])]
|
result (if (vector? result) result [result])]
|
||||||
(recur
|
(recur
|
||||||
(next ids)
|
(next ids)
|
||||||
|
@ -313,22 +319,27 @@
|
||||||
(-> changes (update-indices index-map))))
|
(-> changes (update-indices index-map))))
|
||||||
|
|
||||||
(defn- prepare-duplicate-change
|
(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)]
|
(let [obj (get objects id)]
|
||||||
(if (= :frame (:type obj))
|
(if (= :frame (:type obj))
|
||||||
(prepare-duplicate-frame-change objects page-id unames update-unames! obj delta)
|
(prepare-duplicate-frame-change objects page-id unames update-unames! ids-map obj delta)
|
||||||
(prepare-duplicate-shape-change objects page-id unames update-unames! obj delta (:frame-id obj) (:parent-id obj)))))
|
(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
|
(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)
|
(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))
|
name (dwc/generate-unique-name @unames (:name obj))
|
||||||
_ (update-unames! name)
|
_ (update-unames! name)
|
||||||
|
|
||||||
renamed-obj (assoc obj :id id :name name)
|
new-obj (-> obj
|
||||||
moved-obj (geom/move renamed-obj delta)
|
(assoc :id new-id
|
||||||
parent-id (or parent-id frame-id)
|
:name name
|
||||||
|
:frame-id frame-id)
|
||||||
|
(dissoc :shapes)
|
||||||
|
(geom/move delta)
|
||||||
|
(d/update-when :interactions #(cti/remap-interactions % ids-map objects)))
|
||||||
|
|
||||||
children-changes
|
children-changes
|
||||||
(loop [result []
|
(loop [result []
|
||||||
|
@ -337,47 +348,45 @@
|
||||||
(if (nil? cid)
|
(if (nil? cid)
|
||||||
result
|
result
|
||||||
(let [obj (get objects cid)
|
(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
|
(recur
|
||||||
(into result changes)
|
(into result changes)
|
||||||
(first cids)
|
(first cids)
|
||||||
(rest cids)))))
|
(rest cids)))))]
|
||||||
|
|
||||||
reframed-obj (-> moved-obj
|
|
||||||
(assoc :frame-id frame-id)
|
|
||||||
(dissoc :shapes))]
|
|
||||||
(into [{:type :add-obj
|
(into [{:type :add-obj
|
||||||
:id id
|
:id new-id
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:old-id (:id obj)
|
:old-id (:id obj)
|
||||||
:frame-id frame-id
|
:frame-id frame-id
|
||||||
:parent-id parent-id
|
:parent-id parent-id
|
||||||
:ignore-touched true
|
:ignore-touched true
|
||||||
:obj (dissoc reframed-obj :shapes)}]
|
:obj new-obj}]
|
||||||
children-changes))))
|
children-changes))))
|
||||||
|
|
||||||
(defn- prepare-duplicate-frame-change
|
(defn- prepare-duplicate-frame-change
|
||||||
[objects page-id unames update-unames! obj delta]
|
[objects page-id unames update-unames! ids-map obj delta]
|
||||||
(let [frame-id (uuid/next)
|
(let [new-id (ids-map (:id obj))
|
||||||
frame-name (dwc/generate-unique-name @unames (:name obj))
|
frame-name (dwc/generate-unique-name @unames (:name obj))
|
||||||
_ (update-unames! frame-name)
|
_ (update-unames! frame-name)
|
||||||
|
|
||||||
sch (->> (map #(get objects %) (:shapes obj))
|
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
|
new-frame (-> obj
|
||||||
(assoc :id frame-id)
|
(assoc :id new-id
|
||||||
(assoc :name frame-name)
|
:name frame-name
|
||||||
(assoc :frame-id uuid/zero)
|
:frame-id uuid/zero
|
||||||
(assoc :shapes [])
|
:shapes [])
|
||||||
(geom/move delta))
|
(geom/move delta)
|
||||||
|
(d/update-when :interactions #(cti/remap-interactions % ids-map objects)))
|
||||||
|
|
||||||
fch {:type :add-obj
|
fch {:type :add-obj
|
||||||
:old-id (:id obj)
|
:old-id (:id obj)
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:id frame-id
|
:id new-id
|
||||||
:frame-id uuid/zero
|
:frame-id uuid/zero
|
||||||
:obj frame}]
|
:obj new-frame}]
|
||||||
|
|
||||||
(into [fch] sch)))
|
(into [fch] sch)))
|
||||||
|
|
||||||
|
|
|
@ -377,7 +377,7 @@
|
||||||
[:& page-flows {:flows flows}])
|
[:& page-flows {:flows flows}])
|
||||||
|
|
||||||
[:div.element-set.interactions-options
|
[: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
|
[:div.element-set-title
|
||||||
[:span (tr "workspace.options.interactions")]
|
[:span (tr "workspace.options.interactions")]
|
||||||
[:div.add-page {:on-click add-interaction}
|
[:div.add-page {:on-click add-interaction}
|
||||||
|
@ -385,7 +385,7 @@
|
||||||
[:div.element-set-content
|
[:div.element-set-content
|
||||||
(when (= (count interactions) 0)
|
(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-icon i/plus]
|
||||||
[:div.interactions-help.separator (tr "workspace.options.add-interaction")]])
|
[:div.interactions-help.separator (tr "workspace.options.add-interaction")]])
|
||||||
|
|
|
@ -8,9 +8,8 @@
|
||||||
"Visually show shape interactions in workspace"
|
"Visually show shape interactions in workspace"
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages :as cp]
|
||||||
[app.common.types.interactions :as cti]
|
[app.common.types.interactions :as cti]
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
|
@ -210,7 +209,7 @@
|
||||||
(st/emit! (dw/start-move-overlay-pos index)))]
|
(st/emit! (dw/start-move-overlay-pos index)))]
|
||||||
|
|
||||||
(when dest-shape
|
(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-x (+ (:x orig-frame) (:x position))
|
||||||
marker-y (+ (:y orig-frame) (:y position))
|
marker-y (+ (:y orig-frame) (:y position))
|
||||||
width (:width dest-shape)
|
width (:width dest-shape)
|
||||||
|
@ -320,7 +319,8 @@
|
||||||
:position (:overlay-position interaction)
|
:position (:overlay-position interaction)
|
||||||
:objects objects
|
:objects objects
|
||||||
:hover-disabled? hover-disabled?}]))])))
|
: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)))
|
(not (#{:move :rotate} current-transform)))
|
||||||
[:& interaction-handle {:key (:id shape)
|
[:& interaction-handle {:key (:id shape)
|
||||||
:index nil
|
:index nil
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue