0
Fork 0
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:
alonso.torres 2021-01-15 15:15:02 +01:00 committed by Andrey Antukh
parent 8f57ab343c
commit 20731be1a4
9 changed files with 172 additions and 45 deletions

View file

@ -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)

View file

@ -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))))

View file

@ -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))

View file

@ -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)))

View file

@ -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

View file

@ -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

View file

@ -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))

View file

@ -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})

View file

@ -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}