0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-13 02:28:18 -05:00

Improve shapes vertical ordering implementation.

This commit is contained in:
Andrey Antukh 2019-10-17 16:46:10 +02:00
parent 6f2c952b4b
commit 097a6c924a
4 changed files with 47 additions and 262 deletions

View file

@ -119,69 +119,22 @@
(update-in $ [:pages page :shapes] conj shape-id)) (update-in $ [:pages page :shapes] conj shape-id))
(assoc-in $ [:shapes shape-id] shape)))) (assoc-in $ [:shapes shape-id] shape))))
(defn duplicate-shapes' (defn- duplicate-shape
([state shapes page] [state shape page]
(duplicate-shapes' state shapes page nil)) (let [id (uuid/random)
([state shapes page group] name (generate-unique-name state (str (:name shape) "-copy"))
(letfn [(duplicate-shape [state shape page group] shape (assoc shape :id id :page page :name name)]
(if (= (:type shape) :group) (-> state
(let [id (uuid/random) (update-in [:pages page :shapes] #(into [] (cons id %)))
items (:items shape) (assoc-in [:shapes id] shape))))
name (generate-unique-name state (str (:name shape) "-copy"))
shape (assoc shape
:id id
:page page
:items []
:name name)
state (if (nil? group)
(-> state
(update-in [:pages page :shapes]
#(into [] (cons id %)))
(assoc-in [:shapes id] shape))
(-> state
(update-in [:shapes group :items]
#(into [] (cons id %)))
(assoc-in [:shapes id] shape)))]
(->> (map #(get-in state [:shapes %]) items)
(reverse)
(reduce #(duplicate-shape %1 %2 page id) state)))
(let [id (uuid/random)
name (generate-unique-name state (str (:name shape) "-copy"))
shape (-> (dissoc shape :group)
(assoc :id id :page page :name name)
(merge (when group {:group group})))]
(if (nil? group)
(-> state
(update-in [:pages page :shapes] #(into [] (cons id %)))
(assoc-in [:shapes id] shape))
(-> state
(update-in [:shapes group :items] #(into [] (cons id %)))
(assoc-in [:shapes id] shape))))))]
(reduce #(duplicate-shape %1 %2 page group) state shapes))))
(defn duplicate-shapes (defn duplicate-shapes
([state shapes] ([state shapes]
(duplicate-shapes state shapes nil)) (duplicate-shapes state shapes nil))
([state shapes page] ([state shapes page]
(letfn [(all-toplevel? [coll] (let [shapes (reverse (map #(get-in state [:shapes %]) shapes))
(every? #(nil? (:group %)) coll)) page (or page (:page (first shapes)))]
(all-same-group? [coll] (reduce #(duplicate-shape %1 %2 page) state shapes))))
(let [group (:group (first coll))]
(every? #(= group (:group %)) coll)))]
(let [shapes (reverse (mapv #(get-in state [:shapes %]) shapes))]
(cond
(all-toplevel? shapes)
(let [page (or page (:page (first shapes)))]
(duplicate-shapes' state shapes page))
(all-same-group? shapes)
(let [page (or page (:page (first shapes)))
group (:group (first shapes))]
(duplicate-shapes' state shapes page group))
:else
(let [page (or page (:page (first shapes)))]
(duplicate-shapes' state shapes page)))))))
;; --- Delete Shapes ;; --- Delete Shapes
@ -194,12 +147,12 @@
(defn dissoc-from-page (defn dissoc-from-page
"Given a shape, try to remove its reference from the "Given a shape, try to remove its reference from the
corresponding page." corresponding page."
[state {:keys [id page] :as shape}] [state {:keys [id page type] :as shape}]
;; TODO: handle canvas special case (if (= :canvas type)
(update-in state [:pages page :shapes] (update-in state [:pages page :canvas]
(fn [items] (vec (remove #(= % id) items))))) (fn [items] (vec (remove #(= % id) items))))
(update-in state [:pages page :shapes]
(declare dissoc-shape) (fn [items] (vec (remove #(= % id) items))))))
(defn dissoc-shape (defn dissoc-shape
"Given a shape, removes it from the state." "Given a shape, removes it from the state."
@ -208,109 +161,22 @@
(dissoc-from-page shape) (dissoc-from-page shape)
(dissoc-from-index shape))) (dissoc-from-index shape)))
;; --- Shape Movements ;; --- Shape Vertical Ordering
(defn- drop-at-index (defn order-shape
[index coll v] [state sid opt]
(let [[fst snd] (split-at index coll)] (let [{:keys [page] :as shape} (get-in state [:shapes sid])
(into [] (concat fst [v] snd)))) shapes (get-in state [:pages page :shapes])
index (case opt
(defn drop-relative :top 0
[state loc sid] :down (min (- (count shapes) 1) (inc (index-of shapes sid)))
{:pre [(not (nil? sid))]} :up (max 0 (- (index-of shapes sid) 1))
(let [shape (get-in state [:shapes (first sid)]) :bottom (- (count shapes) 1))]
{:keys [page group]} shape (update-in state [:pages page :shapes]
sid (:id shape) (fn [items]
(let [[fst snd] (->> (remove #(= % sid) items)
shapes (if group (split-at index))]
(get-in state [:shapes group :items]) (into [] (concat fst [sid] snd)))))))
(get-in state [:pages page :shapes]))
index (case loc
:first 0
:after (min (- (count shapes) 1) (inc (index-of shapes sid)))
:before (max 0 (- (index-of shapes sid) 1))
:last (- (count shapes) 1))
state (-> state
(dissoc-from-page shape))
shapes (if group
(get-in state [:shapes group :items])
(get-in state [:pages page :shapes]))
shapes (drop-at-index index shapes sid)]
(if group
(as-> state $
(assoc-in $ [:shapes group :items] shapes)
(update-in $ [:shapes sid] assoc :group group))
(as-> state $
(assoc-in $ [:pages page :shapes] shapes)
(update-in $ [:shapes sid] dissoc :group)))))
(defn drop-aside
[state loc tid sid]
{:pre [(not= tid sid)
(not (nil? tid))
(not (nil? sid))]}
(let [{:keys [page group]} (get-in state [:shapes tid])
source (get-in state [:shapes sid])
state (-> state
(dissoc-from-page source))
shapes (if group
(get-in state [:shapes group :items])
(get-in state [:pages page :shapes]))
index (case loc
:after (inc (index-of shapes tid))
:before (index-of shapes tid))
shapes (drop-at-index index shapes sid)]
(if group
(as-> state $
(assoc-in $ [:shapes group :items] shapes)
(update-in $ [:shapes sid] assoc :group group))
(as-> state $
(assoc-in $ [:pages page :shapes] shapes)
(update-in $ [:shapes sid] dissoc :group)))))
(def drop-after #(drop-aside %1 :after %2 %3))
(def drop-before #(drop-aside %1 :before %2 %3))
(defn drop-inside
[state tid sid]
{:pre [(not= tid sid)]}
(let [source (get-in state [:shapes sid])
state (-> state
(dissoc-from-page source))
shapes (get-in state [:shapes tid :items])]
(if (seq shapes)
(as-> state $
(assoc-in $ [:shapes tid :items] (conj shapes sid))
(update-in $ [:shapes sid] assoc :group tid))
state)))
(defn drop-shape
[state sid tid loc]
(if (= tid sid)
state
(case loc
:inside (drop-inside state tid sid)
:before (drop-before state tid sid)
:after (drop-after state tid sid)
(throw (ex-info "Invalid data" {})))))
(defn move-layer
[state shape loc]
(case loc
:up (drop-relative state :before shape)
:down (drop-relative state :after shape)
:top (drop-relative state :first shape)
:bottom (drop-relative state :last shape)
(throw (ex-info "Invalid data" {}))))
;; --- Shape Selection ;; --- Shape Selection
@ -323,13 +189,6 @@
(geom/overlaps? shape selrect) (geom/overlaps? shape selrect)
(conj acc id) (conj acc id)
(:locked shape)
acc
(= type :group)
(reduce (partial try-match-shape xf selrect)
acc (sequence xf items))
:else :else
acc)) acc))

View file

@ -331,11 +331,12 @@
;; --- Duplicate Selected ;; --- Duplicate Selected
(def duplicate-selected (def duplicate-selected
(reify (ptk/reify ::duplicate-selected
udp/IPageUpdate udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [selected (get-in state [:workspace :selected])] (let [pid (get-in state [:workspace :current])
selected (get-in state [:workspace pid :selected])]
(ds/duplicate-shapes state selected))))) (ds/duplicate-shapes state selected)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -510,16 +511,20 @@
;; --- Move Selected Layer ;; --- Move Selected Layer
(defn move-selected-layer (defn order-selected-shapes
[loc] [loc]
(assert (s/valid? ::direction loc)) (s/assert ::direction loc)
(reify (ptk/reify ::move-selected-layer
udp/IPageUpdate udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [id (get-in state [:workspace :current]) (let [id (get-in state [:workspace :current])
selected (get-in state [:workspace id :selected])] selected (get-in state [:workspace id :selected])]
(ds/move-layer state selected loc))))) (prn "order-selected-shapes" selected)
;; NOTE: multiple selection ordering not supported
(if (pos? (count selected))
(ds/order-shape state (first selected) loc)
state)))))
;; --- Update Shape Position ;; --- Update Shape Position

View file

@ -42,10 +42,10 @@
:ctrl+t #(st/emit! (dw/select-for-drawing :text)) :ctrl+t #(st/emit! (dw/select-for-drawing :text))
:esc #(st/emit! (dw/deselect-all)) :esc #(st/emit! (dw/deselect-all))
:delete #(st/emit! dw/delete-selected) :delete #(st/emit! dw/delete-selected)
:ctrl+up #(st/emit! (dw/move-selected-layer :up)) :ctrl+up #(st/emit! (dw/order-selected-shapes :up))
:ctrl+down #(st/emit! (dw/move-selected-layer :down)) :ctrl+down #(st/emit! (dw/order-selected-shapes :down))
:ctrl+shift+up #(st/emit! (dw/move-selected-layer :top)) :ctrl+shift+up #(st/emit! (dw/order-selected-shapes :top))
:ctrl+shift+down #(st/emit! (dw/move-selected-layer :bottom)) :ctrl+shift+down #(st/emit! (dw/order-selected-shapes :bottom))
:shift+up #(st/emit! (dw/move-selected :up :fast)) :shift+up #(st/emit! (dw/move-selected :up :fast))
:shift+down #(st/emit! (dw/move-selected :down :fast)) :shift+down #(st/emit! (dw/move-selected :down :fast))
:shift+right #(st/emit! (dw/move-selected :right :fast)) :shift+right #(st/emit! (dw/move-selected :right :fast))

View file

@ -100,85 +100,6 @@
;; (pprint result) ;; (pprint result)
(t/is (= result expected)))))) (t/is (= result expected))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Drop Shape (drag and drop and sorted)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; drop shape: move shape before other shape
(t/deftest drop-shape-test1
(let [initial {:pages {1 {:id 1 :shapes [1 2 3]}}
:shapes {1 {:id 1 :page 1}
2 {:id 2 :page 1}
3 {:id 3 :page 1}}}
expected {:pages {1 {:id 1, :shapes [3 1 2]}},
:shapes {1 {:id 1, :page 1},
2 {:id 2, :page 1},
3 {:id 3, :page 1}}}
result (impl/drop-shape initial 3 1 :before)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))
(t/is (vector? (get-in result [:pages 1 :shapes])))))
;; drop shape: move shape after other shape
(t/deftest drop-shape-test2
(let [initial {:pages {1 {:id 1 :shapes [1 2 3]}}
:shapes {1 {:id 1 :page 1}
2 {:id 2 :page 1}
3 {:id 3 :page 1}}}
expected {:pages {1 {:id 1, :shapes [1 3 2]}},
:shapes {1 {:id 1, :page 1},
2 {:id 2, :page 1},
3 {:id 3, :page 1}}}
result (impl/drop-shape initial 3 1 :after)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))
(t/is (vector? (get-in result [:pages 1 :shapes])))))
;; drop shape: move shape before other shape that is part of group.
(t/deftest drop-shape-test3
(let [initial {:pages {1 {:id 1 :shapes [1 3 4]}}
:shapes {1 {:id 1 :page 1 :type :group :items [2]}
2 {:id 2 :page 1 :group 1}
3 {:id 3 :page 1}
4 {:id 4 :page 1}}}
expected {:pages {1 {:id 1, :shapes [1 4]}},
:shapes {1 {:id 1, :page 1, :type :group, :items [3 2]},
2 {:id 2, :page 1, :group 1},
3 {:id 3, :page 1, :group 1},
4 {:id 4, :page 1}}}
result (impl/drop-shape initial 3 2 :before)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))
(t/is (vector? (get-in result [:pages 1 :shapes])))))
;; drop shape: move shape inside group
(t/deftest drop-shape-test4
(let [initial {:pages {1 {:id 1 :shapes [1 3 4]}}
:shapes {1 {:id 1 :page 1 :type :group :items [2]}
2 {:id 2 :page 1 :group 1}
3 {:id 3 :page 1}
4 {:id 4 :page 1}}}
expected {:pages {1 {:id 1, :shapes [1 4]}},
:shapes {1 {:id 1, :page 1, :type :group, :items [2 3]},
2 {:id 2, :page 1, :group 1},
3 {:id 3, :page 1, :group 1},
4 {:id 4, :page 1}}}
result (impl/drop-shape initial 3 1 :inside)]
;; (pprint expected)
;; (pprint result)
(t/is (= result expected))
(t/is (vector? (get-in result [:pages 1 :shapes])))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Delete Shape ;; Delete Shape
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;