0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-25 07:58:49 -05:00

Improve canvas handling on sitemap and workspace.

This commit is contained in:
Andrey Antukh 2019-09-18 18:21:58 +02:00
parent 3d8b3f3040
commit 31ffa73bda
8 changed files with 247 additions and 141 deletions

View file

@ -11,7 +11,7 @@
[cuerdas.core :as str]
[potok.core :as ptk]
[uxbox.main.repo :as rp]
[uxbox.util.data :refer [index-by-id]]
[uxbox.util.data :refer [index-by-id concatv]]
[uxbox.util.spec :as us]
[uxbox.util.timers :as ts]
[uxbox.util.uuid :as uuid]))
@ -97,7 +97,8 @@
(letfn [(pack-shapes [ids]
(mapv #(get-in state [:shapes %]) ids))]
(let [page (get-in state [:pages id])
data {:shapes (pack-shapes (:shapes page))}]
data {:shapes (pack-shapes (concatv (:canvas page)
(:shapes page)))}]
(-> page
(assoc :data data)
(dissoc :shapes)))))
@ -106,13 +107,20 @@
"Unpacks packed page object and assocs it to the
provided state."
[state {:keys [id data] :as page}]
(let [shapes-data (:shapes data [])
shapes (mapv :id shapes-data)
shapes-map (index-by-id shapes-data)
(let [shapes-list (:shapes data [])
shapes (->> shapes-list
(filter #(not= :canvas (:type %)))
(mapv :id))
canvas (->> shapes-list
(filter #(= :canvas (:type %)))
(mapv :id))
shapes-map (index-by-id shapes-list)
page (-> page
(dissoc :data)
(assoc :shapes shapes))]
(assoc :shapes shapes :canvas canvas))]
(-> state
(update :shapes merge shapes-map)
(update :pages assoc id page))))

View file

@ -113,9 +113,11 @@
:page page
:id shape-id
:name shape-name)]
(-> state
(update-in [:pages page :shapes] #(into [] (cons shape-id %)))
(assoc-in [:shapes shape-id] shape))))
(as-> state $
(if (= :canvas (:type shape))
(update-in $ [:pages page :canvas] conj shape-id)
(update-in $ [:pages page :shapes] conj shape-id))
(assoc-in $ [:shapes shape-id] shape))))
(defn duplicate-shapes'
([state shapes page]

View file

@ -553,7 +553,7 @@
(update [_ state]
(assoc-in state [:shapes id :name] name))))
;; --- Change Shape Order (Ordering)
;; --- Change Shape Order (D&D Ordering)
(defn change-shape-order
[{:keys [id index] :as params}]
@ -568,6 +568,22 @@
shapes (vec (concat before [id] after))]
(assoc-in state [:pages page-id :shapes] shapes)))))
;; --- Change Canvas Order (D&D Ordering)
(defn change-canvas-order
[{:keys [id index] :as params}]
(s/assert ::us/uuid id)
(s/assert ::us/number index)
(reify
ptk/UpdateEvent
(update [_ state]
(let [page-id (get-in state [:shapes id :page])
canvas (get-in state [:pages page-id :canvas])
canvas (into [] (remove #(= % id)) canvas)
[before after] (split-at index canvas)
canvas (vec (concat before [id] after))]
(assoc-in state [:pages page-id :canvas] canvas)))))
;; --- Shape Transformations
(defn initial-shape-align
@ -619,6 +635,32 @@
#(update-in % [:shapes id] dissoc :modifier-mtx)
::udp/page-update))))))
(defn rehash-shape-relationship
"Checks shape overlaping with existing canvas, if one or more
overlaps, assigns the shape to the first one."
[id]
(s/assert ::us/uuid id)
(letfn [(overlaps? [canvas shape]
(let [shape1 (geom/shape->rect-shape canvas)
shape2 (geom/shape->rect-shape shape)]
(geom/overlaps? shape1 shape2)))]
(reify
ptk/EventType
(type [_] ::rehash-shape-relationship)
ptk/UpdateEvent
(update [_ state]
(let [shape (get-in state [:shapes id])
xform (comp (map #(get-in state [:shapes %]))
(filter #(overlaps? % shape))
(take 1))
canvas (->> (get-in state [:pages (:page shape) :canvas])
(sequence xform)
(first))]
(if canvas
(update-in state [:shapes id] assoc :canvas (:id canvas))
(update-in state [:shapes id] assoc :canvas nil)))))))
;; --- Start shape "edition mode"
(defn start-edition-mode
@ -777,79 +819,49 @@
;; --- Shape Visibility
(deftype HideShape [id]
udp/IPageUpdate
ptk/UpdateEvent
(update [_ state]
(letfn [(mark-hidden [state id]
(let [shape (get-in state [:shapes id])]
(if (= :group (:type shape))
(as-> state $
(assoc-in $ [:shapes id :hidden] true)
(reduce mark-hidden $ (:items shape)))
(assoc-in state [:shapes id :hidden] true))))]
(mark-hidden state id))))
(defn hide-shape
[id]
{:pre [(uuid? id)]}
(HideShape. id))
(deftype ShowShape [id]
udp/IPageUpdate
ptk/UpdateEvent
(update [_ state]
(letfn [(mark-visible [state id]
(let [shape (get-in state [:shapes id])]
(if (= :group (:type shape))
(as-> state $
(assoc-in $ [:shapes id :hidden] false)
(reduce mark-visible $ (:items shape)))
(assoc-in state [:shapes id :hidden] false))))]
(mark-visible state id))))
(defn show-shape
[id]
{:pre [(uuid? id)]}
(ShowShape. id))
(defn set-hidden-attr
[id value]
(s/assert ::us/uuid id)
(s/assert ::us/boolean value)
(letfn [(impl-set-hidden [state id]
(let [{:keys [type] :as shape} (get-in state [:shapes id])]
(as-> state $
(assoc-in $ [:shapes id :hidden] value)
(if (= :canvas type)
(let [shapes (get-in state [:pages (:page shape) :shapes])
xform (comp (map #(get-in state [:shapes %]))
(filter #(= id (:canvas %)))
(map :id))]
(reduce impl-set-hidden $ (sequence xform shapes)))
$))))]
(reify
udp/IPageUpdate
ptk/UpdateEvent
(update [_ state]
(impl-set-hidden state id)))))
;; --- Shape Blocking
(deftype BlockShape [id]
udp/IPageUpdate
ptk/UpdateEvent
(update [_ state]
(letfn [(mark-blocked [state id]
(let [shape (get-in state [:shapes id])]
(if (= :group (:type shape))
(as-> state $
(assoc-in $ [:shapes id :blocked] true)
(reduce mark-blocked $ (:items shape)))
(assoc-in state [:shapes id :blocked] true))))]
(mark-blocked state id))))
(defn block-shape
[id]
{:pre [(uuid? id)]}
(BlockShape. id))
(deftype UnblockShape [id]
udp/IPageUpdate
ptk/UpdateEvent
(update [_ state]
(letfn [(mark-unblocked [state id]
(let [shape (get-in state [:shapes id])]
(if (= :group (:type shape))
(as-> state $
(assoc-in $ [:shapes id :blocked] false)
(reduce mark-unblocked $ (:items shape)))
(assoc-in state [:shapes id :blocked] false))))]
(mark-unblocked state id))))
(defn unblock-shape
[id]
{:pre [(uuid? id)]}
(UnblockShape. id))
(defn set-blocked-attr
[id value]
(s/assert ::us/uuid id)
(s/assert ::us/boolean value)
(letfn [(impl-set-blocked [state id]
(let [{:keys [type] :as shape} (get-in state [:shapes id])]
(as-> state $
(assoc-in $ [:shapes id :blocked] value)
(if (= :canvas type)
(let [shapes (get-in state [:pages (:page shape) :shapes])
xform (comp (map #(get-in state [:shapes %]))
(filter #(= id (:canvas %)))
(map :id))]
(reduce impl-set-blocked $ (sequence xform shapes)))
$))))]
(reify
udp/IPageUpdate
ptk/UpdateEvent
(update [_ state]
(impl-set-blocked state id)))))
;; --- Shape Locking

View file

@ -56,8 +56,8 @@
[{:keys [status] :as error}]
(js/console.error "Unhandled Error:"
"\n - message:" (ex-message error)
"\n - data:" (pr-str (ex-data error))
"\n - stack:" (.-stack error))
"\n - data:" (pr-str (ex-data error)))
(js/console.error error)
(reset! st/loader false)
(cond
;; Unauthorized or Auth timeout

View file

@ -33,7 +33,8 @@
(->> uws/mouse-position-deltas
(rx/map #(dw/apply-temporal-displacement id %))
(rx/take-until stoper))
(rx/of (dw/materialize-current-modifier id)))))))
(rx/of (dw/materialize-current-modifier id)
(dw/rehash-shape-relationship id)))))))
(def start-move-selected
(reify

View file

@ -17,10 +17,14 @@
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.shapes.icon :as icon]
[uxbox.main.ui.workspace.sortable :refer [use-sortable]]
[uxbox.util.data :refer [classnames]]
[uxbox.util.data :refer [classnames enumerate]]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :refer (tr)]))
(def ^:private shapes-iref
(-> (l/key :shapes)
(l/derive st/state)))
;; --- Helpers
(defn- element-icon
@ -76,21 +80,13 @@
[{:keys [shape selected index] :as props}]
(letfn [(toggle-blocking [event]
(dom/stop-propagation event)
(let [id (:id shape)
blocked? (:blocked shape)]
(if blocked?
(st/emit! (dw/unblock-shape id))
(st/emit! (dw/block-shape id)))))
(let [{:keys [id blocked]} shape]
(st/emit! (dw/set-blocked-attr id (not blocked)))))
(toggle-visibility [event]
(dom/stop-propagation event)
(let [id (:id shape)
hidden? (:hidden shape)]
(if hidden?
(st/emit! (dw/show-shape id))
(st/emit! (dw/hide-shape id)))
(when (contains? selected id)
(st/emit! (dw/select-shape id)))))
(let [{:keys [id hidden]} shape]
(st/emit! (dw/set-hidden-attr id (not hidden)))))
(select-shape [event]
(dom/prevent-default event)
@ -115,7 +111,7 @@
(on-hover [item monitor]
(st/emit! (dw/change-shape-order {:id (:shape-id item)
:index index})))]
:index index})))]
(let [selected? (contains? selected (:id shape))
[dprops dnd-ref] (use-sortable
{:type "layer-item"
@ -141,65 +137,129 @@
[:div.element-icon (element-icon shape)]
[:& layer-name {:shape shape}]]])))
(mf/defc canvas-item
[{:keys [canvas shapes selected index] :as props}]
(letfn [(toggle-blocking [event]
(dom/stop-propagation event)
(let [{:keys [id blocked]} canvas]
(st/emit! (dw/set-blocked-attr id (not blocked)))))
;; --- Layer Canvas
(toggle-visibility [event]
(dom/stop-propagation event)
(let [{:keys [id hidden]} canvas]
(st/emit! (dw/set-hidden-attr id (not hidden)))))
;; (mf/defc layer-canvas
;; [{:keys [canvas selected index] :as props}]
;; (letfn [(select-shape [event]
;; (dom/prevent-default event)
;; (st/emit! (dw/select-canvas (:id canvas))))
;; (let [selected? (contains? selected (:id shape))]
;; [:li {:class (classnames
;; :selected selected?)}
;; [:div.element-list-body {:class (classnames :selected selected?)
;; :on-click select-shape
;; :on-double-click #(dom/stop-propagation %)
;; :draggable true}
;; [:div.element-actions
;; [:div.toggle-element {:class (when-not (:hidden shape) "selected")
;; :on-click toggle-visibility}
;; i/eye]
;; [:div.block-element {:class (when (:blocked shape) "selected")
;; :on-click toggle-blocking}
;; i/lock]]
;; [:div.element-icon (element-icon shape)]
;; [:& layer-name {:shape shape}]]])))
(select-shape [event]
(dom/prevent-default event)
(let [id (:id canvas)]
(cond
(or (:blocked canvas)
(:hidden canvas))
nil
(.-ctrlKey event)
(st/emit! (dw/select-shape id))
(> (count selected) 1)
(st/emit! (dw/deselect-all)
(dw/select-shape id))
:else
(st/emit! (dw/deselect-all)
(dw/select-shape id)))))
(on-drop [item monitor]
(st/emit! (udp/persist-page (:page canvas))))
(on-hover [item monitor]
(prn "canvas-item$hover" (:id canvas))
(st/emit! (dw/change-canvas-order {:id (:canvas-id item)
:index index})))]
(let [selected? (contains? selected (:id canvas))
collapsed? (:collapsed canvas false)
shapes (filter #(= (:canvas (second %)) (:id canvas)) shapes)
[dprops dnd-ref] (use-sortable
{:type "canvas-item"
:data {:canvas-id (:id canvas)
:page-id (:page canvas)
:index index}
:on-hover on-hover
:on-drop on-drop})]
[:li.group {:ref dnd-ref
:class (classnames
:selected selected?
:dragging-TODO (:dragging? dprops))}
[:div.element-list-body {:class (classnames :selected selected?)
:on-click select-shape
:on-double-click #(dom/stop-propagation %)}
[:div.element-actions
[:div.toggle-element {:class (when-not (:hidden canvas) "selected")
:on-click toggle-visibility}
i/eye]
[:div.block-element {:class (when (:blocked canvas) "selected")
:on-click toggle-blocking}
i/lock]]
[:div.element-icon i/folder]
[:& layer-name {:shape canvas}]
[:span.toggle-content
{ ;; :on-click toggle-collapse
:class (when-not collapsed? "inverse")}
i/arrow-slide]]
[:ul
(for [[index shape] shapes]
[:& layer-item {:shape shape
:selected selected
:index index
:key (:id shape)}])]])))
;; --- Layers List
(def ^:private shapes-iref
(-> (l/key :shapes)
(l/derive st/state)))
(def ^:private canvas-iref
(-> (l/key :canvas)
(l/derive st/state)))
(mf/defc layers-list
[{:keys [shapes selected] :as props}]
(let [shapes-map (mf/deref shapes-iref)
canvas-map (mf/deref canvas-iref)
selected-shapes (mf/deref refs/selected-shapes)
selected-canvas (mf/deref refs/selected-canvas)]
[:div.tool-window-content
[:ul.element-list
(for [[index id] (map-indexed vector shapes)]
[:& layer-item {:shape (get shapes-map id)
:selected selected-shapes
:index index
:key id}])]]))
[:ul.element-list
(for [[index shape] shapes]
[:& layer-item {:shape shape
:selected selected
:index index
:key (:id shape)}])])
(mf/defc canvas-list
[{:keys [shapes canvas selected] :as props}]
[:ul.element-list
(for [[index item] canvas]
[:& canvas-item {:canvas item
:shapes shapes
:selected selected
:index index
:key (:id item)}])])
;; --- Layers Toolbox
(mf/defc layers-toolbox
[{:keys [page selected] :as props}]
(let [on-click #(st/emit! (dw/toggle-flag :layers))
selected (mf/deref refs/selected-shapes)]
selected (mf/deref refs/selected-shapes)
shapes-by-id (mf/deref shapes-iref)
canvas (->> (:canvas page)
(map #(get shapes-by-id %))
(enumerate))
all-shapes (->> (:shapes page)
(map #(get shapes-by-id %)))
shapes (->> all-shapes
(filter #(not (:canvas %)))
(enumerate))
all-shapes (enumerate all-shapes)]
[:div#layers.tool-window
[:div.tool-window-bar
[:div.tool-window-icon i/layers]
[:span (tr "ds.settings.layers")]
[:div.tool-window-close {:on-click on-click} i/close]]
[:& layers-list {:shapes (:shapes page)
:selected selected}]]))
[:div.tool-window-content
[:& canvas-list {:canvas canvas
:shapes all-shapes
:selected selected}]
[:& layers-list {:shapes shapes
:selected selected}]]]))

View file

@ -246,6 +246,9 @@
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
(when page
[:*
(for [id (reverse (:canvas page))]
[:& uus/shape-component {:id id :key id}])
(for [id (reverse (:shapes page))]
[:& uus/shape-component {:id id :key id}])

View file

@ -79,6 +79,26 @@
(disj s v)
(conj s v)))
(defn enumerate
([items] (enumerate items 0))
([items start]
(loop [idx start
items items
res []]
(if (empty? items)
res
(recur (inc idx)
(rest items)
(conj res [idx (first items)]))))))
(defn concatv
[& colls]
(loop [colls colls
result []]
(if (seq colls)
(recur (rest colls) (reduce conj result (first colls)))
result)))
(defn seek
([pred coll]
(seek pred coll nil))