mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
✨ Add paste to replace (Cmd+Shift+V) (#9033)
Paste clipboard contents in place of the currently selected shape, inheriting its position, parent, and z-index. The replaced shape is deleted in the same transaction for a single undo step. Signed-off-by: eureka0928 <meobius123@gmail.com>
This commit is contained in:
parent
f0c68fb826
commit
42ebee88d6
@ -33,6 +33,7 @@
|
||||
- Make links in comments clickable (by @eureka0928) [Github #1602](https://github.com/penpot/penpot/issues/1602)
|
||||
- Add visibility toggle for strokes (by @eureka0928) [Github #7438](https://github.com/penpot/penpot/issues/7438)
|
||||
- Sort asset library subfolders alphabetically at every nesting level (by @eureka0928) [Github #2572](https://github.com/penpot/penpot/issues/2572)
|
||||
- Add Paste to replace (Cmd+Shift+V) to replace the selected shape with clipboard contents (by @eureka0928) [Github #4240](https://github.com/penpot/penpot/issues/4240)
|
||||
- Differentiate incoming and outgoing interaction link colors (by @claytonlin1110) [Github #7794](https://github.com/penpot/penpot/issues/7794)
|
||||
- Add guide locking and fix locked elements not selectable in viewer (by @Dexterity104) [Github #8358](https://github.com/penpot/penpot/issues/8358)
|
||||
- Apply styles to selection (by @AzazelN28) [Taiga #13647](https://tree.taiga.io/project/penpot/task/13647)
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.geom.shapes.grid-layout :as gslg]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.transit :as t]
|
||||
[app.common.types.component :as ctc]
|
||||
@ -260,7 +261,7 @@
|
||||
:allowHTMLPaste (features/active-feature? @st/state "text-editor/v2-html-paste")})
|
||||
|
||||
(defn- create-paste-from-blob
|
||||
[in-viewport?]
|
||||
[in-viewport? replace?]
|
||||
(fn [blob]
|
||||
(let [type (.-type blob)]
|
||||
(cond
|
||||
@ -281,7 +282,9 @@
|
||||
(rx/filter map?)
|
||||
(rx/map
|
||||
(fn [pdata]
|
||||
(assoc pdata :in-viewport in-viewport?)))
|
||||
(-> pdata
|
||||
(assoc :in-viewport in-viewport?)
|
||||
(assoc :replace replace?))))
|
||||
(rx/mapcat
|
||||
(fn [pdata]
|
||||
(case (:type pdata)
|
||||
@ -293,8 +296,6 @@
|
||||
(->> (rx/from (.text blob))
|
||||
(rx/map paste-text))))))
|
||||
|
||||
(def default-paste-from-blob (create-paste-from-blob false))
|
||||
|
||||
(defn- clipboard-permission-error?
|
||||
"Check if the given error is a clipboard permission error
|
||||
(NotAllowedError DOMException)."
|
||||
@ -313,14 +314,15 @@
|
||||
|
||||
(defn paste-from-clipboard
|
||||
"Perform a `paste` operation using the Clipboard API."
|
||||
[]
|
||||
(ptk/reify ::paste-from-clipboard
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (clipboard/from-navigator default-options)
|
||||
(rx/mapcat default-paste-from-blob)
|
||||
(rx/take 1)
|
||||
(rx/catch on-clipboard-permission-error)))))
|
||||
([] (paste-from-clipboard nil))
|
||||
([{:keys [replace?]}]
|
||||
(ptk/reify ::paste-from-clipboard
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (clipboard/from-navigator default-options)
|
||||
(rx/mapcat (create-paste-from-blob false (boolean replace?)))
|
||||
(rx/take 1)
|
||||
(rx/catch on-clipboard-permission-error))))))
|
||||
|
||||
(defn paste-from-event
|
||||
"Perform a `paste` operation from user emmited event."
|
||||
@ -337,7 +339,7 @@
|
||||
(if is-editing?
|
||||
(rx/empty)
|
||||
(->> (clipboard/from-synthetic-clipboard-event event default-options)
|
||||
(rx/mapcat (create-paste-from-blob in-viewport?))))))))
|
||||
(rx/mapcat (create-paste-from-blob in-viewport? false))))))))
|
||||
|
||||
(defn copy-selected-svg
|
||||
[]
|
||||
@ -722,7 +724,7 @@
|
||||
(update change :obj process-rchange-shape media-idx)
|
||||
change))
|
||||
|
||||
(calculate-paste-position [state pobjects selected position]
|
||||
(calculate-paste-position [state pobjects selected position replace-id]
|
||||
(let [page-objects (dsh/lookup-page-objects state)
|
||||
selected-objs (map (d/getf pobjects) selected)
|
||||
first-selected-obj (first selected-objs)
|
||||
@ -736,9 +738,20 @@
|
||||
tree-root (get-tree-root-shapes pobjects)
|
||||
only-one-root-shape? (and
|
||||
(< 1 (count pobjects))
|
||||
(= 1 (count tree-root)))]
|
||||
(= 1 (count tree-root)))
|
||||
replaced (some->> replace-id (get page-objects))]
|
||||
|
||||
(cond
|
||||
;; Paste in place: center pasted content on the replaced shape and
|
||||
;; reparent to its container. The replaced shape is deleted below
|
||||
;; so the new content takes its z-index slot.
|
||||
(some? replaced)
|
||||
(let [delta (gpt/subtract (gsh/shape->center replaced)
|
||||
(grc/rect->center wrapper))
|
||||
parent-id (:parent-id replaced)
|
||||
target-index (cfh/get-position-on-parent page-objects replace-id)]
|
||||
[parent-id delta target-index])
|
||||
|
||||
;; Paste next to selected frame, if selected is itself or of the same size as the copied
|
||||
(and (selected-frame? state)
|
||||
(or (any-same-frame-from-selected? state (keys pobjects))
|
||||
@ -854,10 +867,17 @@
|
||||
|
||||
position (deref ms/mouse-position)
|
||||
|
||||
;; Replace mode is only valid with a single selected shape.
|
||||
;; In that case we drop the pasted content at its position and
|
||||
;; delete it in the same transaction.
|
||||
page-selected (dsh/lookup-selected state)
|
||||
replace-id (when (and (:replace pdata) (= 1 (count page-selected)))
|
||||
(first page-selected))
|
||||
|
||||
;; Calculate position for the pasted elements
|
||||
[candidate-parent-id
|
||||
delta
|
||||
index] (calculate-paste-position state objects selected position)
|
||||
index] (calculate-paste-position state objects selected position replace-id)
|
||||
|
||||
page-objects (:objects page)
|
||||
|
||||
@ -899,6 +919,10 @@
|
||||
(map :id)
|
||||
(pcb/resize-parents changes))
|
||||
|
||||
changes (if (some? replace-id)
|
||||
(second (cls/generate-delete-shapes changes #{replace-id} {}))
|
||||
changes)
|
||||
|
||||
orig-shapes (map (d/getf all-objects) selected)
|
||||
|
||||
children-after (-> (pcb/get-objects changes)
|
||||
|
||||
@ -104,6 +104,11 @@
|
||||
:subsections [:edit]
|
||||
:fn (constantly nil)}
|
||||
|
||||
:paste-replace {:tooltip (ds/meta (ds/shift "V"))
|
||||
:command (ds/c-mod "shift+v")
|
||||
:subsections [:edit]
|
||||
:fn #(emit-when-no-readonly (dw/paste-from-clipboard {:replace? true}))}
|
||||
|
||||
:copy-props {:tooltip (ds/meta (ds/alt "c"))
|
||||
:command (ds/c-mod "alt+c")
|
||||
:subsections [:edit]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user