mirror of
https://github.com/penpot/penpot.git
synced 2025-01-25 07:58:49 -05:00
✨ Adding shapes over the selected shapes
This commit is contained in:
parent
8f57ab343c
commit
20731be1a4
9 changed files with 172 additions and 45 deletions
|
@ -58,6 +58,7 @@
|
|||
(d/export helpers/frame-id-by-position)
|
||||
(d/export helpers/set-touched-group)
|
||||
(d/export helpers/touched-group?)
|
||||
(d/export helpers/get-base-shape)
|
||||
|
||||
;; Process changes
|
||||
(d/export changes/process-changes)
|
||||
|
|
|
@ -311,3 +311,16 @@
|
|||
[shape group]
|
||||
((or (:touched shape) #{}) group))
|
||||
|
||||
(defn get-base-shape
|
||||
"Selects the shape that will be the base to add the shapes over"
|
||||
[objects selected]
|
||||
(let [;; Gets the tree-index for all the shapes
|
||||
indexed-shapes (indexed-shapes objects)
|
||||
|
||||
;; Filters the selected and retrieve a list of ids
|
||||
sorted-ids (->> indexed-shapes
|
||||
(filter (comp selected second))
|
||||
(map second))]
|
||||
|
||||
;; The first id will be the top-most
|
||||
(get objects (first sorted-ids))))
|
||||
|
|
|
@ -1172,7 +1172,6 @@
|
|||
(if (= :image (:type item))
|
||||
(let [img-part {:id (:id metadata)
|
||||
:name (:name item)
|
||||
:file-name (path/baseName (:path metadata))
|
||||
:file-data (::data item)}]
|
||||
(update res :images conj img-part))
|
||||
res)))
|
||||
|
@ -1252,7 +1251,7 @@
|
|||
(js/console.error "ERROR" e))))))))
|
||||
|
||||
(defn paste-from-event
|
||||
[event]
|
||||
[event in-viewport?]
|
||||
(ptk/reify ::paste-from-event
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -1263,7 +1262,7 @@
|
|||
decoded-data (and (t/transit? text-data) (t/decode text-data))]
|
||||
(cond
|
||||
(seq image-data) (rx/from (map paste-image image-data))
|
||||
decoded-data (rx/of (paste-shape decoded-data))
|
||||
decoded-data (rx/of (paste-shape decoded-data in-viewport?))
|
||||
(string? text-data) (rx/of (paste-text text-data))
|
||||
:else (rx/empty)))
|
||||
(catch :default err
|
||||
|
@ -1277,9 +1276,8 @@
|
|||
(= :frame (get-in objects [(first selected) :type]))))))
|
||||
|
||||
(defn- paste-shape
|
||||
[{:keys [selected objects images] :as data}]
|
||||
(letfn [
|
||||
;; Given a file-id and img (part generated by the
|
||||
[{:keys [selected objects images] :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/data-url->blob (:file-data imgpart))
|
||||
|
@ -1289,7 +1287,7 @@
|
|||
:file-id file-id
|
||||
:content blob
|
||||
:is-local true}))
|
||||
(rx/mapcat #(rp/mutation! :upload-media-object %))
|
||||
(rx/mapcat #(rp/mutation! :upload-file-media-object %))
|
||||
(rx/map (fn [media]
|
||||
(assoc media :prev-id (:id imgpart))))))
|
||||
|
||||
|
@ -1306,35 +1304,82 @@
|
|||
mdata)))
|
||||
item))
|
||||
|
||||
(calculate-paste-position [state mouse-pos in-viewport?]
|
||||
(let [page-objects (dwc/lookup-page-objects state)
|
||||
selected-objs (map #(get objects %) selected)
|
||||
has-frame? (d/seek #(= (:type %) :frame) selected-objs)
|
||||
page-selected (get-in state [:workspace-local :selected])
|
||||
wrapper (gsh/selection-rect selected-objs)
|
||||
orig-pos (gpt/point (:x1 wrapper) (:y1 wrapper))]
|
||||
(cond
|
||||
(and (selected-frame? state) (not has-frame?))
|
||||
(let [frame-id (first page-selected)
|
||||
delta (get page-objects frame-id)]
|
||||
[frame-id frame-id delta])
|
||||
|
||||
(empty? page-selected)
|
||||
(let [frame-id (cp/frame-id-by-position page-objects mouse-pos)
|
||||
delta (gpt/subtract mouse-pos orig-pos)]
|
||||
[frame-id frame-id delta])
|
||||
|
||||
:else
|
||||
(let [base (cp/get-base-shape page-objects page-selected)
|
||||
index (cp/position-on-parent (:id base) page-objects)
|
||||
frame-id (:frame-id base)
|
||||
parent-id (:parent-id base)
|
||||
delta (if in-viewport?
|
||||
(gpt/subtract mouse-pos orig-pos)
|
||||
(gpt/subtract (gpt/point (:selrect base)) orig-pos))]
|
||||
[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]
|
||||
(let [set-index (fn [[result index] id]
|
||||
[(assoc result id index) (inc index)])
|
||||
|
||||
map-ids (when index
|
||||
(->> (vals objects)
|
||||
(filter #(not (selected (:parent-id %))))
|
||||
(map :id)
|
||||
(reduce set-index [{} (inc index)])
|
||||
first))]
|
||||
(if (and (= :add-obj (:type change))
|
||||
(contains? map-ids (:old-id change)))
|
||||
(assoc change :index (get map-ids (:old-id change)))
|
||||
change)))
|
||||
|
||||
;; Procceed with the standard shape paste procediment.
|
||||
(do-paste [state mouse-pos media]
|
||||
(let [media-idx (d/index-by :prev-id media)
|
||||
selected-objs (map #(get objects %) selected)
|
||||
wrapper (gsh/selection-rect selected-objs)
|
||||
orig-pos (gpt/point (:x1 wrapper) (:y1 wrapper))
|
||||
page-id (:current-page-id state)
|
||||
|
||||
page-objects (dwc/lookup-page-objects state page-id)
|
||||
page-selected (get-in state [:workspace-local :selected])
|
||||
;; Calculate position for the pasted elements
|
||||
[frame-id parent-id delta index] (calculate-paste-position state mouse-pos in-viewport?)
|
||||
|
||||
[frame-id delta]
|
||||
(if (selected-frame? state)
|
||||
[(first page-selected)
|
||||
(get page-objects (first page-selected))]
|
||||
[(cp/frame-id-by-position page-objects mouse-pos)
|
||||
(gpt/subtract mouse-pos orig-pos)])
|
||||
|
||||
objects (d/mapm (fn [_ v] (assoc v :frame-id frame-id :parent-id frame-id)) objects)
|
||||
objects (->> objects
|
||||
(d/mapm (fn [_ shape]
|
||||
(-> shape
|
||||
(assoc :frame-id frame-id)
|
||||
(assoc :parent-id parent-id)))))
|
||||
|
||||
page-id (:current-page-id state)
|
||||
unames (-> (dwc/lookup-page-objects state page-id)
|
||||
(dwc/retrieve-used-names))
|
||||
|
||||
rchanges (dws/prepare-duplicate-changes objects page-id unames selected delta)
|
||||
rchanges (mapv (partial process-rchange media-idx) rchanges)
|
||||
rchanges (->> (dws/prepare-duplicate-changes objects page-id unames selected delta)
|
||||
(mapv (partial process-rchange media-idx))
|
||||
(mapv (partial change-add-obj-index objects selected index)))
|
||||
|
||||
uchanges (mapv #(array-map :type :del-obj :page-id page-id :id (:id %))
|
||||
(reverse rchanges))
|
||||
|
||||
;; 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))
|
||||
|
||||
rchanges (conj rchanges {:type :reg-objects
|
||||
:page-id page-id
|
||||
:shapes new-objects-ids})
|
||||
|
||||
selected (->> rchanges
|
||||
(filter #(selected (:old-id %)))
|
||||
(map #(get-in % [:obj :id]))
|
||||
|
@ -1556,7 +1601,8 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
;; Not interrupt when we're editing a path
|
||||
(let [edition-id (get-in state [:workspace-local :edition])
|
||||
(let [edition-id (or (get-in state [:workspace-drawing :object :id])
|
||||
(get-in state [:workspace-local :edition]))
|
||||
path-edit-mode (get-in state [:workspace-local :edit-path edition-id :edit-mode])]
|
||||
(if-not (= :draw path-edit-mode)
|
||||
(rx/of :interrupt (deselect-all true))
|
||||
|
|
|
@ -524,22 +524,47 @@
|
|||
(update-in [:workspace-local :hover] disj id)
|
||||
(update :workspace-local dissoc :edition))))))
|
||||
|
||||
(defn get-shape-layer-position
|
||||
[objects selected attrs]
|
||||
|
||||
(cond
|
||||
(= :frame (:type attrs))
|
||||
[uuid/zero uuid/zero nil]
|
||||
|
||||
(empty? selected)
|
||||
(let [position @ms/mouse-position
|
||||
frame-id (:frame-id attrs (cp/frame-id-by-position objects position))]
|
||||
[frame-id frame-id nil])
|
||||
|
||||
:else
|
||||
(let [shape (cp/get-base-shape objects selected)
|
||||
index (cp/position-on-parent (:id shape) objects)
|
||||
{:keys [frame-id parent-id]} shape]
|
||||
[frame-id parent-id (inc index)])))
|
||||
|
||||
(defn add-shape-changes
|
||||
[page-id attrs]
|
||||
(let [id (:id attrs)
|
||||
frame-id (:frame-id attrs)
|
||||
shape (gpr/setup-proportions attrs)
|
||||
[page-id objects selected attrs]
|
||||
(let [id (:id attrs)
|
||||
shape (gpr/setup-proportions attrs)
|
||||
|
||||
default-attrs (if (= :frame (:type shape))
|
||||
cp/default-frame-attrs
|
||||
cp/default-shape-attrs)
|
||||
|
||||
shape (merge default-attrs shape)
|
||||
|
||||
[frame-id parent-id index] (get-shape-layer-position objects selected attrs)
|
||||
|
||||
redo-changes [{:type :add-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:obj shape}]
|
||||
:parent-id parent-id
|
||||
:index index
|
||||
:obj shape}
|
||||
{:type :reg-objects
|
||||
:page-id page-id
|
||||
:shapes [id]}]
|
||||
undo-changes [{:type :del-obj
|
||||
:page-id page-id
|
||||
:id id}]]
|
||||
|
@ -560,16 +585,15 @@
|
|||
(retrieve-used-names)
|
||||
(generate-unique-name (:name attrs)))
|
||||
|
||||
position @ms/mouse-position
|
||||
frame-id (if (= :frame (:type attrs))
|
||||
uuid/zero
|
||||
(or (:frame-id attrs)
|
||||
(cp/frame-id-by-position objects position)))
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
|
||||
[rchanges uchanges] (add-shape-changes page-id (assoc attrs
|
||||
:id id
|
||||
:frame-id frame-id
|
||||
:name name))]
|
||||
[rchanges uchanges] (add-shape-changes
|
||||
page-id
|
||||
objects
|
||||
selected
|
||||
(-> attrs
|
||||
(assoc :id id )
|
||||
(assoc :name name)))]
|
||||
(rx/concat
|
||||
(rx/of (commit-changes rchanges uchanges {:commit-local? true})
|
||||
(select-shapes (d/ordered-set id)))
|
||||
|
|
|
@ -60,8 +60,7 @@
|
|||
(rx/of (dwc/start-undo-transaction))
|
||||
(rx/empty))
|
||||
|
||||
(rx/of (dws/deselect-all)
|
||||
(dwc/add-shape shape))
|
||||
(rx/of (dwc/add-shape shape))
|
||||
|
||||
(if (= :frame (:type shape))
|
||||
(->> (uw/ask! {:cmd :selection/query
|
||||
|
|
|
@ -548,8 +548,7 @@
|
|||
(update-in [:workspace-local :edit-path id :content-modifiers (inc index)] assoc
|
||||
:c1x dx :c1y dy)
|
||||
(update-in [:workspace-local :edit-path id :content-modifiers index] assoc
|
||||
:x dx :y dy :c2x dx :c2y dy)
|
||||
)))))
|
||||
:x dx :y dy :c2x dx :c2y dy))))))
|
||||
|
||||
(defn modify-handler [id index prefix dx dy match-opposite?]
|
||||
(ptk/reify ::modify-point
|
||||
|
|
|
@ -251,6 +251,33 @@
|
|||
|
||||
(def ^:private change->name #(get-in % [:obj :name]))
|
||||
|
||||
(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.
|
||||
|
||||
index-map is a map that goes from parent-id => vector([id index-in-parent])"
|
||||
[changes index-map]
|
||||
(let [inc-indices
|
||||
(fn [[offset result] [id index]]
|
||||
[(inc offset) (conj result [id (+ index offset)])])
|
||||
|
||||
fix-indices
|
||||
(fn [_ entry]
|
||||
(->> entry
|
||||
(sort-by second)
|
||||
(reduce inc-indices [1 []])
|
||||
(second)
|
||||
(into {})))
|
||||
|
||||
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
|
||||
|
@ -269,6 +296,18 @@
|
|||
(into chgs result)))
|
||||
chgs)))
|
||||
|
||||
(defn duplicate-changes-update-indices
|
||||
"Parses the change set when duplicating to set-up the appropiate indices"
|
||||
[objects ids changes]
|
||||
|
||||
(let [process-id
|
||||
(fn [index-map id]
|
||||
(let [parent-id (get-in objects [id :parent-id])
|
||||
parent-index (cp/position-on-parent id objects)]
|
||||
(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 names id delta]
|
||||
(let [obj (get objects id)]
|
||||
|
@ -347,7 +386,9 @@
|
|||
delta (gpt/point 0 0)
|
||||
unames (dwc/retrieve-used-names objects)
|
||||
|
||||
rchanges (prepare-duplicate-changes objects page-id unames selected delta)
|
||||
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))
|
||||
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
frame-id (cp/frame-id-by-position objects {:x x :y y})
|
||||
selected (get-in state [:workspace-local :selected])
|
||||
|
||||
[width height] (svg-dimensions data)
|
||||
x (- x (/ width 2))
|
||||
|
@ -152,7 +153,7 @@
|
|||
(fn add-svg-child [parent-id root-shape [unames [rchs uchs]] [index {:keys [content] :as data}]]
|
||||
(let [shape (parse-svg-element root-shape data unames)
|
||||
shape-id (:id shape)
|
||||
[rch1 uch1] (dwc/add-shape-changes page-id shape)
|
||||
[rch1 uch1] (dwc/add-shape-changes page-id objects selected shape)
|
||||
|
||||
;; Mov-objects won't have undo because we "delete" the object in the undo of the
|
||||
;; previous operation
|
||||
|
@ -176,7 +177,7 @@
|
|||
root-shape (create-raw-svg svg-name frame-id x y width height data)
|
||||
root-id (:id root-shape)
|
||||
|
||||
changes (dwc/add-shape-changes page-id root-shape)
|
||||
changes (dwc/add-shape-changes page-id objects selected root-shape)
|
||||
|
||||
[_ [rchanges uchanges]] (reduce (partial add-svg-child root-id root-shape) [unames changes] (d/enumerate (:content data)))]
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
|
|
|
@ -254,6 +254,7 @@
|
|||
zoom-view-ref (mf/use-ref nil)
|
||||
last-position (mf/use-var nil)
|
||||
disable-paste (mf/use-var false)
|
||||
in-viewport? (mf/use-var false)
|
||||
drawing (mf/deref refs/workspace-drawing)
|
||||
drawing-tool (:tool drawing)
|
||||
drawing-obj (:object drawing)
|
||||
|
@ -553,7 +554,7 @@
|
|||
;; paste the content into the workspace
|
||||
(let [tag-name (-> event dom/get-target dom/get-tag-name)]
|
||||
(when (and (not (#{"INPUT" "TEXTAREA"} tag-name)) (not @disable-paste))
|
||||
(st/emit! (dw/paste-from-event event))))))
|
||||
(st/emit! (dw/paste-from-event event @in-viewport?))))))
|
||||
|
||||
on-resize
|
||||
(mf/use-callback
|
||||
|
@ -640,6 +641,8 @@
|
|||
:on-mouse-up on-mouse-up
|
||||
:on-pointer-down on-pointer-down
|
||||
:on-pointer-up on-pointer-up
|
||||
:on-pointer-enter #(reset! in-viewport? true)
|
||||
:on-pointer-leave #(reset! in-viewport? false)
|
||||
:on-drag-enter on-drag-enter
|
||||
:on-drag-over on-drag-over
|
||||
:on-drop on-drop}
|
||||
|
|
Loading…
Add table
Reference in a new issue