0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-08 16:18:11 -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))
(assoc-in $ [:shapes shape-id] shape))))
(defn duplicate-shapes'
([state shapes page]
(duplicate-shapes' state shapes page nil))
([state shapes page group]
(letfn [(duplicate-shape [state shape page group]
(if (= (:type shape) :group)
(let [id (uuid/random)
items (:items 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-shape
[state shape page]
(let [id (uuid/random)
name (generate-unique-name state (str (:name shape) "-copy"))
shape (assoc shape :id id :page page :name name)]
(-> state
(update-in [:pages page :shapes] #(into [] (cons id %)))
(assoc-in [:shapes id] shape))))
(defn duplicate-shapes
([state shapes]
(duplicate-shapes state shapes nil))
([state shapes page]
(letfn [(all-toplevel? [coll]
(every? #(nil? (:group %)) coll))
(all-same-group? [coll]
(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)))))))
(let [shapes (reverse (map #(get-in state [:shapes %]) shapes))
page (or page (:page (first shapes)))]
(reduce #(duplicate-shape %1 %2 page) state shapes))))
;; --- Delete Shapes
@ -194,12 +147,12 @@
(defn dissoc-from-page
"Given a shape, try to remove its reference from the
corresponding page."
[state {:keys [id page] :as shape}]
;; TODO: handle canvas special case
(update-in state [:pages page :shapes]
(fn [items] (vec (remove #(= % id) items)))))
(declare dissoc-shape)
[state {:keys [id page type] :as shape}]
(if (= :canvas type)
(update-in state [:pages page :canvas]
(fn [items] (vec (remove #(= % id) items))))
(update-in state [:pages page :shapes]
(fn [items] (vec (remove #(= % id) items))))))
(defn dissoc-shape
"Given a shape, removes it from the state."
@ -208,109 +161,22 @@
(dissoc-from-page shape)
(dissoc-from-index shape)))
;; --- Shape Movements
;; --- Shape Vertical Ordering
(defn- drop-at-index
[index coll v]
(let [[fst snd] (split-at index coll)]
(into [] (concat fst [v] snd))))
(defn drop-relative
[state loc sid]
{:pre [(not (nil? sid))]}
(let [shape (get-in state [:shapes (first sid)])
{:keys [page group]} shape
sid (:id shape)
shapes (if group
(get-in state [:shapes group :items])
(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" {}))))
(defn order-shape
[state sid opt]
(let [{:keys [page] :as shape} (get-in state [:shapes sid])
shapes (get-in state [:pages page :shapes])
index (case opt
:top 0
:down (min (- (count shapes) 1) (inc (index-of shapes sid)))
:up (max 0 (- (index-of shapes sid) 1))
:bottom (- (count shapes) 1))]
(update-in state [:pages page :shapes]
(fn [items]
(let [[fst snd] (->> (remove #(= % sid) items)
(split-at index))]
(into [] (concat fst [sid] snd)))))))
;; --- Shape Selection
@ -323,13 +189,6 @@
(geom/overlaps? shape selrect)
(conj acc id)
(:locked shape)
acc
(= type :group)
(reduce (partial try-match-shape xf selrect)
acc (sequence xf items))
:else
acc))

View file

@ -331,11 +331,12 @@
;; --- Duplicate Selected
(def duplicate-selected
(reify
(ptk/reify ::duplicate-selected
udp/IPageUpdate
ptk/UpdateEvent
(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)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -510,16 +511,20 @@
;; --- Move Selected Layer
(defn move-selected-layer
(defn order-selected-shapes
[loc]
(assert (s/valid? ::direction loc))
(reify
(s/assert ::direction loc)
(ptk/reify ::move-selected-layer
udp/IPageUpdate
ptk/UpdateEvent
(update [_ state]
(let [id (get-in state [:workspace :current])
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

View file

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

View file

@ -100,85 +100,6 @@
;; (pprint result)
(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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;