mirror of
https://github.com/penpot/penpot.git
synced 2025-03-16 01:31:22 -05:00
Merge pull request #227 from uxbox/331/auto-open-layers
331/auto open layers
This commit is contained in:
commit
d0defe5d93
5 changed files with 111 additions and 79 deletions
|
@ -72,6 +72,12 @@
|
||||||
(into result (calculate-invalid-targets child-id objects)))]
|
(into result (calculate-invalid-targets child-id objects)))]
|
||||||
(reduce reduce-fn result children)))
|
(reduce reduce-fn result children)))
|
||||||
|
|
||||||
|
(defn- valid-frame-target
|
||||||
|
[shape-id parent-id objects]
|
||||||
|
(let [shape (get objects shape-id)]
|
||||||
|
(or (not= (:type shape) :frame)
|
||||||
|
(= parent-id uuid/zero))))
|
||||||
|
|
||||||
(defn- insert-at-index
|
(defn- insert-at-index
|
||||||
[shapes index ids]
|
[shapes index ids]
|
||||||
(let [[before after] (split-at index shapes)
|
(let [[before after] (split-at index shapes)
|
||||||
|
@ -389,8 +395,9 @@
|
||||||
;; Check if the move from shape-id -> parent-id is valid
|
;; Check if the move from shape-id -> parent-id is valid
|
||||||
is-valid-move
|
is-valid-move
|
||||||
(fn [shape-id]
|
(fn [shape-id]
|
||||||
(let [invalid (calculate-invalid-targets shape-id (:objects data))]
|
(let [invalid-targets (calculate-invalid-targets shape-id (:objects data))]
|
||||||
(not (invalid parent-id))))
|
(and (not (invalid-targets parent-id))
|
||||||
|
(valid-frame-target shape-id parent-id (:objects data)))))
|
||||||
|
|
||||||
valid? (every? is-valid-move shapes)
|
valid? (every? is-valid-move shapes)
|
||||||
|
|
||||||
|
|
|
@ -916,13 +916,11 @@
|
||||||
|
|
||||||
;; --- Change Shape Order (D&D Ordering)
|
;; --- Change Shape Order (D&D Ordering)
|
||||||
|
|
||||||
;; TODO: pending UNDO
|
|
||||||
|
|
||||||
(defn relocate-shape
|
(defn relocate-shape
|
||||||
[id parent-id index]
|
[id parent-id to-index]
|
||||||
(us/verify ::us/uuid id)
|
(us/verify ::us/uuid id)
|
||||||
(us/verify ::us/uuid parent-id)
|
(us/verify ::us/uuid parent-id)
|
||||||
(us/verify number? index)
|
(us/verify number? to-index)
|
||||||
|
|
||||||
(ptk/reify ::relocate-shape
|
(ptk/reify ::relocate-shape
|
||||||
dwc/IUpdateGroup
|
dwc/IUpdateGroup
|
||||||
|
@ -931,12 +929,18 @@
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [page-id (:current-page-id state)
|
(let [page-id (:current-page-id state)
|
||||||
|
objects (get-in state [:workspace-data page-id :objects])
|
||||||
|
parent (get objects (cp/get-parent id objects))
|
||||||
|
current-index (d/index-of (:shapes parent) id)
|
||||||
selected (get-in state [:workspace-local :selected])]
|
selected (get-in state [:workspace-local :selected])]
|
||||||
(rx/of (dwc/commit-changes [{:type :mov-objects
|
(rx/of (dwc/commit-changes [{:type :mov-objects
|
||||||
:parent-id parent-id
|
:parent-id parent-id
|
||||||
:index index
|
:index to-index
|
||||||
|
:shapes (vec selected)}]
|
||||||
|
[{:type :mov-objects
|
||||||
|
:parent-id (:id parent)
|
||||||
|
:index current-index
|
||||||
:shapes (vec selected)}]
|
:shapes (vec selected)}]
|
||||||
[]
|
|
||||||
{:commit-local? true}))))))
|
{:commit-local? true}))))))
|
||||||
|
|
||||||
;; --- Change Page Order (D&D Ordering)
|
;; --- Change Page Order (D&D Ordering)
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
[uxbox.util.transit :as t]
|
[uxbox.util.transit :as t]
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.dom :as dom]
|
||||||
[uxbox.util.webapi :as wapi]
|
[uxbox.util.webapi :as wapi]
|
||||||
|
[uxbox.util.timers :as ts]
|
||||||
["mousetrap" :as mousetrap])
|
["mousetrap" :as mousetrap])
|
||||||
(:import goog.events.EventType))
|
(:import goog.events.EventType))
|
||||||
|
|
||||||
|
@ -31,7 +32,6 @@
|
||||||
#js [ob])
|
#js [ob])
|
||||||
state))
|
state))
|
||||||
|
|
||||||
|
|
||||||
(s/def ::shortcuts
|
(s/def ::shortcuts
|
||||||
(s/map-of ::us/string fn?))
|
(s/map-of ::us/string fn?))
|
||||||
|
|
||||||
|
@ -66,18 +66,6 @@
|
||||||
|
|
||||||
[toggle @state]))
|
[toggle @state]))
|
||||||
|
|
||||||
;; (defn- extract-type
|
|
||||||
;; [dt]
|
|
||||||
;; (let [types (unchecked-get dt "types")
|
|
||||||
;; total (alength types)]
|
|
||||||
;; (loop [i 0]
|
|
||||||
;; (if (= i total)
|
|
||||||
;; nil
|
|
||||||
;; (if-let [match (re-find #"dnd/(.+)" (aget types i))]
|
|
||||||
;; (second match)
|
|
||||||
;; (recur (inc i)))))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn invisible-image
|
(defn invisible-image
|
||||||
[]
|
[]
|
||||||
(let [img (js/Image.)
|
(let [img (js/Image.)
|
||||||
|
@ -97,118 +85,145 @@
|
||||||
:else :center)
|
:else :center)
|
||||||
(if (> ypos thold) :bot :top))))
|
(if (> ypos thold) :bot :top))))
|
||||||
|
|
||||||
|
(defn- set-timer
|
||||||
|
[state ms func]
|
||||||
|
(assoc state :timer (ts/schedule ms func)))
|
||||||
|
|
||||||
|
(defn- cancel-timer
|
||||||
|
[state]
|
||||||
|
(let [timer (:timer state)]
|
||||||
|
(if timer
|
||||||
|
(do
|
||||||
|
(rx/dispose! timer)
|
||||||
|
(dissoc state :timer))
|
||||||
|
state)))
|
||||||
|
|
||||||
|
;; The dnd interface is broken in several ways. This is the official documentation
|
||||||
|
;; https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
|
||||||
|
;;
|
||||||
|
;; And there is some discussion of the problems and many uncomplete solutions
|
||||||
|
;; https://github.com/lolmaus/jquery.dragbetter/#what-this-is-all-about
|
||||||
|
;; https://www.w3schools.com/jsref/event_relatedtarget.asp
|
||||||
|
;; https://stackoverflow.com/questions/14194324/firefox-firing-dragleave-when-dragging-over-text?noredirect=1&lq=1
|
||||||
|
;; https://stackoverflow.com/questions/7110353/html5-dragleave-fired-when-hovering-a-child-element
|
||||||
|
|
||||||
|
;; This function is useful to debug the erratic dnd interface behaviour when something weird occurs
|
||||||
|
;; (defn- trace
|
||||||
|
;; [event data label]
|
||||||
|
;; (js/console.log
|
||||||
|
;; label
|
||||||
|
;; "[" (:name data) "]"
|
||||||
|
;; (if (.-currentTarget event) (.-textContent (.-currentTarget event)) "null")
|
||||||
|
;; (if (.-relatedTarget event) (.-textContent (.-relatedTarget event)) "null")))
|
||||||
|
|
||||||
(defn use-sortable
|
(defn use-sortable
|
||||||
[& {:keys [type data on-drop on-drag detect-center?] :as opts}]
|
[& {:keys [data-type data on-drop on-drag on-hold detect-center?] :as opts}]
|
||||||
(let [ref (mf/use-ref)
|
(let [ref (mf/use-ref)
|
||||||
state (mf/use-state {})
|
state (mf/use-state {:over nil
|
||||||
|
:timer nil})
|
||||||
|
|
||||||
on-drag-start
|
on-drag-start
|
||||||
(fn [event]
|
(fn [event]
|
||||||
;; (dom/prevent-default event)
|
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
|
;; (trace event data "drag-start")
|
||||||
(let [dtrans (unchecked-get event "dataTransfer")]
|
(let [dtrans (unchecked-get event "dataTransfer")]
|
||||||
(.setDragImage dtrans (invisible-image) 0 0)
|
(.setDragImage dtrans (invisible-image) 0 0)
|
||||||
(set! (.-effectAllowed dtrans) "move")
|
(set! (.-effectAllowed dtrans) "move")
|
||||||
(.setData dtrans "application/json" (t/encode data))
|
(.setData dtrans data-type (t/encode data))
|
||||||
;; (.setData dtrans (str "dnd/" type) "")
|
|
||||||
(when (fn? on-drag)
|
(when (fn? on-drag)
|
||||||
(on-drag data))
|
(on-drag data))))
|
||||||
(swap! state (fn [state]
|
|
||||||
(if (:dragging? state)
|
on-drag-enter
|
||||||
state
|
(fn [event]
|
||||||
(assoc state :dragging? true))))))
|
(dom/prevent-default event) ;; prevent default to allow drag enter
|
||||||
|
(let [target (.-currentTarget event)
|
||||||
|
related (.-relatedTarget event)]
|
||||||
|
(when-not (.contains target related) ;; ignore events triggered by elements that are
|
||||||
|
(dom/stop-propagation event) ;; children of the drop target
|
||||||
|
;; (trace event data "drag-enter")
|
||||||
|
(when (fn? on-hold)
|
||||||
|
(swap! state (fn [state]
|
||||||
|
(-> state
|
||||||
|
(cancel-timer)
|
||||||
|
(set-timer 1000 on-hold))))))))
|
||||||
|
|
||||||
on-drag-over
|
on-drag-over
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/stop-propagation event)
|
(let [dtrans (unchecked-get event "dataTransfer")
|
||||||
(dom/prevent-default event)
|
target (.-currentTarget event)
|
||||||
|
related (.-relatedTarget event)
|
||||||
(let [target (dom/get-target event)
|
|
||||||
dtrans (unchecked-get event "dataTransfer")
|
|
||||||
ypos (unchecked-get event "offsetY")
|
ypos (unchecked-get event "offsetY")
|
||||||
height (unchecked-get target "clientHeight")
|
height (unchecked-get target "clientHeight")
|
||||||
side (drop-side height ypos detect-center?)]
|
side (drop-side height ypos detect-center?)]
|
||||||
|
(when (.includes (.-types dtrans) data-type)
|
||||||
(set! (.-dropEffect dtrans) "move")
|
(dom/prevent-default event) ;; prevent default to allow drag over
|
||||||
(set! (.-effectAllowed dtrans) "move")
|
(when-not (.contains target related)
|
||||||
|
(dom/stop-propagation event)
|
||||||
(swap! state update :over (fn [state]
|
;; (trace event data "drag-over")
|
||||||
(if (not= state side)
|
(swap! state assoc :over side)))))
|
||||||
side
|
|
||||||
state)))))
|
|
||||||
|
|
||||||
;; on-drag-enter
|
|
||||||
;; (fn [event]
|
|
||||||
;; (dom/prevent-default event)
|
|
||||||
;; (dom/stop-propagation event)
|
|
||||||
;; (let [dtrans (unchecked-get event "dataTransfer")
|
|
||||||
;; ty (extract-type dt)]
|
|
||||||
;; (when (= ty type)
|
|
||||||
;; #_(js/console.log "on-drag-enter" (:name data) ty type)
|
|
||||||
;; #_(swap! state (fn [state]
|
|
||||||
;; (if (:over? state)
|
|
||||||
;; state
|
|
||||||
;; (assoc state :over? true)))))))
|
|
||||||
|
|
||||||
on-drag-leave
|
on-drag-leave
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(let [target (.-currentTarget event)
|
(let [target (.-currentTarget event)
|
||||||
related (.-relatedTarget event)]
|
related (.-relatedTarget event)]
|
||||||
(when-not (.contains target related)
|
(when-not (.contains target related)
|
||||||
;; (js/console.log "on-drag-leave" (:name data))
|
(dom/stop-propagation event)
|
||||||
|
;; (trace event data "drag-leave")
|
||||||
(swap! state (fn [state]
|
(swap! state (fn [state]
|
||||||
(if (:over state)
|
(-> state
|
||||||
(dissoc state :over)
|
(cancel-timer)
|
||||||
state))))))
|
(dissoc :over)))))))
|
||||||
|
|
||||||
on-drop'
|
on-drop'
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(let [target (dom/get-target event)
|
;; (trace event data "drop")
|
||||||
dtrans (unchecked-get event "dataTransfer")
|
(let [dtrans (unchecked-get event "dataTransfer")
|
||||||
dtdata (.getData dtrans "application/json")
|
dtdata (.getData dtrans data-type)
|
||||||
|
|
||||||
|
target (.-currentTarget event)
|
||||||
ypos (unchecked-get event "offsetY")
|
ypos (unchecked-get event "offsetY")
|
||||||
height (unchecked-get target "clientHeight")
|
height (unchecked-get target "clientHeight")
|
||||||
side (drop-side height ypos detect-center?)]
|
side (drop-side height ypos detect-center?)]
|
||||||
|
|
||||||
;; TODO: seems unnecessary
|
|
||||||
(swap! state (fn [state]
|
(swap! state (fn [state]
|
||||||
(cond-> state
|
(-> state
|
||||||
(:dragging? state) (dissoc :dragging?)
|
(cancel-timer)
|
||||||
(:over state) (dissoc :over))))
|
(dissoc :over))))
|
||||||
|
|
||||||
(when (fn? on-drop)
|
(when (fn? on-drop)
|
||||||
(on-drop side (t/decode dtdata)))))
|
(on-drop side (t/decode dtdata)))))
|
||||||
|
|
||||||
on-drag-end
|
on-drag-end
|
||||||
(fn [event]
|
(fn [event]
|
||||||
|
;; (trace event data "drag-end")
|
||||||
(swap! state (fn [state]
|
(swap! state (fn [state]
|
||||||
(cond-> state
|
(-> state
|
||||||
(:dragging? state) (dissoc :dragging?)
|
(cancel-timer)
|
||||||
(:over state) (dissoc :over)))))
|
(dissoc :over)))))
|
||||||
|
|
||||||
on-mount
|
on-mount
|
||||||
(fn []
|
(fn []
|
||||||
(let [dom (mf/ref-val ref)]
|
(let [dom (mf/ref-val ref)]
|
||||||
(.setAttribute dom "draggable" true)
|
(.setAttribute dom "draggable" true)
|
||||||
(.setAttribute dom "data-type" type)
|
|
||||||
|
|
||||||
(.addEventListener dom "dragstart" on-drag-start false)
|
(.addEventListener dom "dragstart" on-drag-start false)
|
||||||
;; (.addEventListener dom "dragenter" on-drag-enter false)
|
(.addEventListener dom "dragenter" on-drag-enter false)
|
||||||
(.addEventListener dom "dragover" on-drag-over false)
|
(.addEventListener dom "dragover" on-drag-over false)
|
||||||
(.addEventListener dom "dragleave" on-drag-leave true)
|
(.addEventListener dom "dragleave" on-drag-leave true)
|
||||||
(.addEventListener dom "drop" on-drop' false)
|
(.addEventListener dom "drop" on-drop' false)
|
||||||
(.addEventListener dom "dragend" on-drag-end false)
|
(.addEventListener dom "dragend" on-drag-end false)
|
||||||
#(do
|
#(do
|
||||||
(.removeEventListener dom "dragstart" on-drag-start)
|
(.removeEventListener dom "dragstart" on-drag-start)
|
||||||
;; (.removeEventListener dom "dragenter" on-drag-enter)
|
(.removeEventListener dom "dragenter" on-drag-enter)
|
||||||
(.removeEventListener dom "dragover" on-drag-over)
|
(.removeEventListener dom "dragover" on-drag-over)
|
||||||
(.removeEventListener dom "dragleave" on-drag-leave)
|
(.removeEventListener dom "dragleave" on-drag-leave)
|
||||||
(.removeEventListener dom "drop" on-drop')
|
(.removeEventListener dom "drop" on-drop')
|
||||||
(.removeEventListener dom "dragend" on-drag-end))))]
|
(.removeEventListener dom "dragend" on-drag-end))))]
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps type data on-drop)
|
(mf/deps data on-drop)
|
||||||
on-mount)
|
on-mount)
|
||||||
|
|
||||||
[(deref state) ref]))
|
[(deref state) ref]))
|
||||||
|
|
||||||
|
|
|
@ -153,14 +153,20 @@
|
||||||
(fn [side {:keys [id] :as data}]
|
(fn [side {:keys [id] :as data}]
|
||||||
(if (= side :center)
|
(if (= side :center)
|
||||||
(st/emit! (dw/relocate-shape id (:id item) 0))
|
(st/emit! (dw/relocate-shape id (:id item) 0))
|
||||||
(let [index (if (= side :top) (inc index) index)
|
(let [to-index (if (= side :top) (inc index) index)
|
||||||
parent-id (cp/get-parent (:id item) objects)]
|
parent-id (cp/get-parent (:id item) objects)]
|
||||||
(st/emit! (dw/relocate-shape id parent-id index)))))
|
(st/emit! (dw/relocate-shape id parent-id to-index)))))
|
||||||
|
|
||||||
|
on-hold
|
||||||
|
(fn []
|
||||||
|
(when-not expanded?
|
||||||
|
(st/emit! (dw/toggle-collapse (:id item)))))
|
||||||
|
|
||||||
[dprops dref] (hooks/use-sortable
|
[dprops dref] (hooks/use-sortable
|
||||||
:type (str (:frame-id item))
|
:data-type "uxbox/layer"
|
||||||
:on-drop on-drop
|
:on-drop on-drop
|
||||||
:on-drag on-drag
|
:on-drag on-drag
|
||||||
|
:on-hold on-hold
|
||||||
:detect-center? container?
|
:detect-center? container?
|
||||||
:data {:id (:id item)
|
:data {:id (:id item)
|
||||||
:index index
|
:index index
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
(st/emit! (dw/relocate-page id index))))
|
(st/emit! (dw/relocate-page id index))))
|
||||||
|
|
||||||
[dprops dref] (hooks/use-sortable
|
[dprops dref] (hooks/use-sortable
|
||||||
:type "page"
|
:data-type "uxbox/page"
|
||||||
:on-drop on-drop
|
:on-drop on-drop
|
||||||
:data {:id (:id page)
|
:data {:id (:id page)
|
||||||
:index index
|
:index index
|
||||||
|
|
Loading…
Add table
Reference in a new issue