0
Fork 0
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:
Andrey Antukh 2020-05-25 13:50:44 +02:00 committed by GitHub
commit d0defe5d93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 111 additions and 79 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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]))

View file

@ -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

View file

@ -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