0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-26 08:29:42 -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] [cuerdas.core :as str]
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.main.repo :as rp] [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.spec :as us]
[uxbox.util.timers :as ts] [uxbox.util.timers :as ts]
[uxbox.util.uuid :as uuid])) [uxbox.util.uuid :as uuid]))
@ -97,7 +97,8 @@
(letfn [(pack-shapes [ids] (letfn [(pack-shapes [ids]
(mapv #(get-in state [:shapes %]) ids))] (mapv #(get-in state [:shapes %]) ids))]
(let [page (get-in state [:pages id]) (let [page (get-in state [:pages id])
data {:shapes (pack-shapes (:shapes page))}] data {:shapes (pack-shapes (concatv (:canvas page)
(:shapes page)))}]
(-> page (-> page
(assoc :data data) (assoc :data data)
(dissoc :shapes))))) (dissoc :shapes)))))
@ -106,13 +107,20 @@
"Unpacks packed page object and assocs it to the "Unpacks packed page object and assocs it to the
provided state." provided state."
[state {:keys [id data] :as page}] [state {:keys [id data] :as page}]
(let [shapes-data (:shapes data []) (let [shapes-list (:shapes data [])
shapes (mapv :id shapes-data)
shapes-map (index-by-id 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 page (-> page
(dissoc :data) (dissoc :data)
(assoc :shapes shapes))] (assoc :shapes shapes :canvas canvas))]
(-> state (-> state
(update :shapes merge shapes-map) (update :shapes merge shapes-map)
(update :pages assoc id page)))) (update :pages assoc id page))))

View file

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

View file

@ -553,7 +553,7 @@
(update [_ state] (update [_ state]
(assoc-in state [:shapes id :name] name)))) (assoc-in state [:shapes id :name] name))))
;; --- Change Shape Order (Ordering) ;; --- Change Shape Order (D&D Ordering)
(defn change-shape-order (defn change-shape-order
[{:keys [id index] :as params}] [{:keys [id index] :as params}]
@ -568,6 +568,22 @@
shapes (vec (concat before [id] after))] shapes (vec (concat before [id] after))]
(assoc-in state [:pages page-id :shapes] shapes))))) (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 ;; --- Shape Transformations
(defn initial-shape-align (defn initial-shape-align
@ -619,6 +635,32 @@
#(update-in % [:shapes id] dissoc :modifier-mtx) #(update-in % [:shapes id] dissoc :modifier-mtx)
::udp/page-update)))))) ::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" ;; --- Start shape "edition mode"
(defn start-edition-mode (defn start-edition-mode
@ -777,79 +819,49 @@
;; --- Shape Visibility ;; --- Shape Visibility
(deftype HideShape [id] (defn set-hidden-attr
udp/IPageUpdate [id value]
ptk/UpdateEvent (s/assert ::us/uuid id)
(update [_ state] (s/assert ::us/boolean value)
(letfn [(mark-hidden [state id] (letfn [(impl-set-hidden [state id]
(let [shape (get-in state [:shapes id])] (let [{:keys [type] :as shape} (get-in state [:shapes id])]
(if (= :group (:type shape)) (as-> state $
(as-> state $ (assoc-in $ [:shapes id :hidden] value)
(assoc-in $ [:shapes id :hidden] true) (if (= :canvas type)
(reduce mark-hidden $ (:items shape))) (let [shapes (get-in state [:pages (:page shape) :shapes])
(assoc-in state [:shapes id :hidden] true))))] xform (comp (map #(get-in state [:shapes %]))
(mark-hidden state id)))) (filter #(= id (:canvas %)))
(map :id))]
(defn hide-shape (reduce impl-set-hidden $ (sequence xform shapes)))
[id] $))))]
{:pre [(uuid? id)]} (reify
(HideShape. id)) udp/IPageUpdate
ptk/UpdateEvent
(deftype ShowShape [id] (update [_ state]
udp/IPageUpdate (impl-set-hidden state id)))))
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))
;; --- Shape Blocking ;; --- Shape Blocking
(deftype BlockShape [id] (defn set-blocked-attr
udp/IPageUpdate [id value]
ptk/UpdateEvent (s/assert ::us/uuid id)
(update [_ state] (s/assert ::us/boolean value)
(letfn [(mark-blocked [state id] (letfn [(impl-set-blocked [state id]
(let [shape (get-in state [:shapes id])] (let [{:keys [type] :as shape} (get-in state [:shapes id])]
(if (= :group (:type shape)) (as-> state $
(as-> state $ (assoc-in $ [:shapes id :blocked] value)
(assoc-in $ [:shapes id :blocked] true) (if (= :canvas type)
(reduce mark-blocked $ (:items shape))) (let [shapes (get-in state [:pages (:page shape) :shapes])
(assoc-in state [:shapes id :blocked] true))))] xform (comp (map #(get-in state [:shapes %]))
(mark-blocked state id)))) (filter #(= id (:canvas %)))
(map :id))]
(defn block-shape (reduce impl-set-blocked $ (sequence xform shapes)))
[id] $))))]
{:pre [(uuid? id)]} (reify
(BlockShape. id)) udp/IPageUpdate
ptk/UpdateEvent
(deftype UnblockShape [id] (update [_ state]
udp/IPageUpdate (impl-set-blocked state id)))))
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))
;; --- Shape Locking ;; --- Shape Locking

View file

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

View file

@ -33,7 +33,8 @@
(->> uws/mouse-position-deltas (->> uws/mouse-position-deltas
(rx/map #(dw/apply-temporal-displacement id %)) (rx/map #(dw/apply-temporal-displacement id %))
(rx/take-until stoper)) (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 (def start-move-selected
(reify (reify

View file

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

View file

@ -79,6 +79,26 @@
(disj s v) (disj s v)
(conj 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 (defn seek
([pred coll] ([pred coll]
(seek pred coll nil)) (seek pred coll nil))