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:
parent
3d8b3f3040
commit
31ffa73bda
8 changed files with 247 additions and 141 deletions
|
@ -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))))
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}]]]))
|
||||
|
|
|
@ -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}])
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Reference in a new issue