diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 1e83700a9..619240400 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -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) diff --git a/common/app/common/pages/helpers.cljc b/common/app/common/pages/helpers.cljc index c27f68103..97bbab500 100644 --- a/common/app/common/pages/helpers.cljc +++ b/common/app/common/pages/helpers.cljc @@ -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)))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 4fe933772..d59573b8e 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -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)) diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index eddb1e4b2..7f2874a30 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -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))) diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs index fef13e245..f0676a6d1 100644 --- a/frontend/src/app/main/data/workspace/drawing/common.cljs +++ b/frontend/src/app/main/data/workspace/drawing/common.cljs @@ -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 diff --git a/frontend/src/app/main/data/workspace/drawing/path.cljs b/frontend/src/app/main/data/workspace/drawing/path.cljs index a26fde33e..5f181cffd 100644 --- a/frontend/src/app/main/data/workspace/drawing/path.cljs +++ b/frontend/src/app/main/data/workspace/drawing/path.cljs @@ -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 diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 64f331edb..97471fb09 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -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)) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 359b6770a..ea6b4a072 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -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}) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index bd17c15f6..466e363e7 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -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}