mirror of
https://github.com/penpot/penpot.git
synced 2025-01-24 07:29:08 -05:00
✨ Change drag component to instantiate on enter the viewport
This commit is contained in:
parent
f75da999dc
commit
497b581576
9 changed files with 121 additions and 67 deletions
|
@ -36,6 +36,7 @@
|
|||
[app.main.data.workspace.specialized-panel :as dwsp]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.thumbnails :as dwt]
|
||||
[app.main.data.workspace.transforms :as dwtr]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.features :as features]
|
||||
[app.main.features.pointer-map :as fpmap]
|
||||
|
@ -534,34 +535,38 @@
|
|||
(defn instantiate-component
|
||||
"Create a new shape in the current page, from the component with the given id
|
||||
in the given file library. Then selects the newly created instance."
|
||||
[file-id component-id position]
|
||||
(dm/assert! (uuid? file-id))
|
||||
(dm/assert! (uuid? component-id))
|
||||
(dm/assert! (gpt/point? position))
|
||||
(ptk/reify ::instantiate-component
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page (wsh/lookup-page state)
|
||||
libraries (wsh/get-libraries state)
|
||||
([file-id component-id position]
|
||||
(instantiate-component file-id component-id position nil))
|
||||
([file-id component-id position {:keys [start-move? initial-point]}]
|
||||
(dm/assert! (uuid? file-id))
|
||||
(dm/assert! (uuid? component-id))
|
||||
(dm/assert! (gpt/point? position))
|
||||
(ptk/reify ::instantiate-component
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page (wsh/lookup-page state)
|
||||
libraries (wsh/get-libraries state)
|
||||
|
||||
objects (:objects page)
|
||||
changes (-> (pcb/empty-changes it (:id page))
|
||||
(pcb/with-objects objects))
|
||||
objects (:objects page)
|
||||
changes (-> (pcb/empty-changes it (:id page))
|
||||
(pcb/with-objects objects))
|
||||
|
||||
[new-shape changes]
|
||||
(dwlh/generate-instantiate-component changes
|
||||
objects
|
||||
file-id
|
||||
component-id
|
||||
position
|
||||
page
|
||||
libraries)
|
||||
undo-id (js/Symbol)]
|
||||
(rx/of (dwu/start-undo-transaction undo-id)
|
||||
(dch/commit-changes changes)
|
||||
(ptk/data-event :layout/update [(:id new-shape)])
|
||||
(dws/select-shapes (d/ordered-set (:id new-shape)))
|
||||
(dwu/commit-undo-transaction undo-id))))))
|
||||
[new-shape changes]
|
||||
(dwlh/generate-instantiate-component changes
|
||||
objects
|
||||
file-id
|
||||
component-id
|
||||
position
|
||||
page
|
||||
libraries)
|
||||
undo-id (js/Symbol)]
|
||||
(rx/of (dwu/start-undo-transaction undo-id)
|
||||
(dch/commit-changes changes)
|
||||
(ptk/data-event :layout/update [(:id new-shape)])
|
||||
(dws/select-shapes (d/ordered-set (:id new-shape)))
|
||||
(when start-move?
|
||||
(dwtr/start-move initial-point #{(:id new-shape)}))
|
||||
(dwu/commit-undo-transaction undo-id)))))))
|
||||
|
||||
(defn detach-component
|
||||
"Remove all references to components in the shape with the given id,
|
||||
|
|
|
@ -496,7 +496,7 @@
|
|||
(when-let [node (dom/get-element-by-class "ghost-outline")]
|
||||
(dom/set-property! node "transform" (gmt/translate-matrix move-vector))))))
|
||||
|
||||
(defn- start-move
|
||||
(defn start-move
|
||||
([from-position] (start-move from-position nil))
|
||||
([from-position ids]
|
||||
(ptk/reify ::start-move
|
||||
|
|
|
@ -50,13 +50,6 @@
|
|||
(fn []
|
||||
(st/emit! (dsc/pop-shortcuts key))))))
|
||||
|
||||
(defn invisible-image
|
||||
[]
|
||||
(let [img (js/Image.)
|
||||
imd "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="]
|
||||
(set! (.-src img) imd)
|
||||
img))
|
||||
|
||||
(defn- set-timer
|
||||
[state ms func]
|
||||
(assoc state :timer (ts/schedule ms func)))
|
||||
|
@ -128,7 +121,7 @@
|
|||
(do
|
||||
(dom/stop-propagation event)
|
||||
(dnd/set-data! event data-type data)
|
||||
(dnd/set-drag-image! event (invisible-image))
|
||||
(dnd/set-drag-image! event (dnd/invisible-image))
|
||||
(dnd/set-allowed-effect! event "move")
|
||||
(when (fn? on-drag)
|
||||
(on-drag data)))))
|
||||
|
|
|
@ -472,13 +472,26 @@
|
|||
(mf/use-fn
|
||||
(mf/deps file-id)
|
||||
(fn [component event]
|
||||
;; dnd api only allow to acces to the dataTransfer data on on-drop (https://html.spec.whatwg.org/dev/dnd.html#concept-dnd-p)
|
||||
;; We need to know if the dragged element is from the local library on on-drag-enter, so we need to keep the info elsewhere
|
||||
(set-drag-data! {:local? local?})
|
||||
|
||||
(dnd/set-data! event "penpot/component" {:file-id file-id
|
||||
:component component})
|
||||
(dnd/set-allowed-effect! event "move")))
|
||||
(let [file-data
|
||||
(d/nilv (dm/get-in @refs/workspace-libraries [file-id :data]) @refs/workspace-data)
|
||||
|
||||
shape-main
|
||||
(ctf/get-component-root file-data component)]
|
||||
|
||||
;; dnd api only allow to acces to the dataTransfer data on on-drop (https://html.spec.whatwg.org/dev/dnd.html#concept-dnd-p)
|
||||
;; We need to know if the dragged element is from the local library on on-drag-enter, so we need to keep the info elsewhere
|
||||
(set-drag-data! {:file-id file-id
|
||||
:component component
|
||||
:shape shape-main
|
||||
:local? local?})
|
||||
|
||||
(dnd/set-data! event "penpot/component" true)
|
||||
|
||||
;; Remove the ghost image for componentes because we're going to instantiate it on the viewport
|
||||
(dnd/set-drag-image! event (dnd/invisible-image))
|
||||
|
||||
(dnd/set-allowed-effect! event "move"))))
|
||||
|
||||
on-show-main
|
||||
(mf/use-fn
|
||||
|
@ -569,4 +582,3 @@
|
|||
{:option-name (tr "workspace.shape.menu.show-main")
|
||||
:id "assets-show-main-component"
|
||||
:option-handler on-show-main})]}]]]))
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
(dom/focus! textarea))))
|
||||
on-delete-annotation
|
||||
(mf/use-callback
|
||||
(mf/deps shape)
|
||||
(mf/deps (:id shape))
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (modal/show
|
||||
|
@ -98,7 +98,7 @@
|
|||
(dw/update-component-annotation component-id nil)))}))))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps shape)
|
||||
(mf/deps (:id shape))
|
||||
(fn []
|
||||
(initialize)
|
||||
(when (and (not creating?) (:id-for-create workspace-annotations)) ;; cleanup set-annotations-id-for-create if we aren't on the marked component
|
||||
|
|
|
@ -174,9 +174,12 @@
|
|||
on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?)
|
||||
on-context-menu (actions/on-context-menu hover hover-ids workspace-read-only?)
|
||||
on-double-click (actions/on-double-click hover hover-ids hover-top-frame-id drawing-path? base-objects edition drawing-tool z? workspace-read-only?)
|
||||
on-drag-enter (actions/on-drag-enter)
|
||||
|
||||
comp-inst-ref (mf/use-ref false)
|
||||
on-drag-enter (actions/on-drag-enter comp-inst-ref)
|
||||
on-drag-over (actions/on-drag-over move-stream)
|
||||
on-drop (actions/on-drop file)
|
||||
on-drag-end (actions/on-drag-over comp-inst-ref)
|
||||
on-drop (actions/on-drop file comp-inst-ref)
|
||||
on-pointer-down (actions/on-pointer-down @hover selected edition drawing-tool text-editing? node-editing? grid-editing?
|
||||
drawing-path? create-comment? space? panning z? workspace-read-only?)
|
||||
|
||||
|
@ -365,6 +368,7 @@
|
|||
:on-double-click on-double-click
|
||||
:on-drag-enter on-drag-enter
|
||||
:on-drag-over on-drag-over
|
||||
:on-drag-end on-drag-end
|
||||
:on-drop on-drop
|
||||
:on-pointer-down on-pointer-down
|
||||
:on-pointer-enter on-pointer-enter
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
[app.main.data.workspace.specialized-panel :as-alias dwsp]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.workspace.sidebar.assets.components :as wsac]
|
||||
[app.main.ui.workspace.viewport.viewport-ref :as uwvv]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.dom.dnd :as dnd]
|
||||
|
@ -28,7 +29,8 @@
|
|||
[app.util.keyboard :as kbd]
|
||||
[app.util.mouse :as mse]
|
||||
[app.util.object :as obj]
|
||||
[app.util.timers :as timers]
|
||||
[app.util.rxops :refer [throttle-fn]]
|
||||
[app.util.timers :as ts]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -216,7 +218,7 @@
|
|||
(st/emit! (mse/->MouseEvent :double-click ctrl? shift? alt? meta?))
|
||||
|
||||
;; Emit asynchronously so the double click to exit shapes won't break
|
||||
(timers/schedule
|
||||
(ts/schedule
|
||||
(fn []
|
||||
(when (and (not drawing-path?) shape)
|
||||
(cond
|
||||
|
@ -244,7 +246,7 @@
|
|||
workspace-read-only?)
|
||||
(let [position (dom/get-client-position event)]
|
||||
;; Delayed callback because we need to wait to the previous context menu to be closed
|
||||
(timers/schedule
|
||||
(ts/schedule
|
||||
#(st/emit!
|
||||
(if (some? @hover)
|
||||
(dw/show-shape-context-menu {:position position
|
||||
|
@ -290,7 +292,7 @@
|
|||
|
||||
;; We store this so in Firefox the middle button won't do a paste of the content
|
||||
(reset! disable-paste true)
|
||||
(timers/schedule #(reset! disable-paste false)))
|
||||
(ts/schedule #(reset! disable-paste false)))
|
||||
|
||||
(st/emit! (dw/finish-panning)
|
||||
(dw/finish-zooming))))))
|
||||
|
@ -400,9 +402,28 @@
|
|||
(st/emit! (dw/update-viewport-position {:x #(+ % (/ delta-x zoom))
|
||||
:y #(+ % (/ delta-y zoom))}))))))))))
|
||||
|
||||
(defn on-drag-enter []
|
||||
(defn on-drag-enter
|
||||
[comp-inst-ref]
|
||||
(mf/use-callback
|
||||
(fn [e]
|
||||
(let [component-inst? (mf/ref-val comp-inst-ref)]
|
||||
(when (and (dnd/has-type? e "penpot/component")
|
||||
(dom/class? (dom/get-target e) "viewport-controls")
|
||||
(not component-inst?))
|
||||
(let [point (gpt/point (.-clientX e) (.-clientY e))
|
||||
viewport-coord (uwvv/point->viewport point)
|
||||
{:keys [component file-id shape]} @wsac/drag-data*
|
||||
|
||||
;; shape (get-in component [:objects (:id component)])
|
||||
final-x (- (:x viewport-coord) (/ (:width shape) 2))
|
||||
final-y (- (:y viewport-coord) (/ (:height shape) 2))]
|
||||
|
||||
(mf/set-ref-val! comp-inst-ref true)
|
||||
(st/emit! (dwl/instantiate-component
|
||||
file-id
|
||||
(:id component)
|
||||
(gpt/point final-x final-y)
|
||||
{:start-move? true :initial-point viewport-coord})))))
|
||||
(when (or (dnd/has-type? e "penpot/shape")
|
||||
(dnd/has-type? e "penpot/component")
|
||||
(dnd/has-type? e "Files")
|
||||
|
@ -410,8 +431,19 @@
|
|||
(dnd/has-type? e "text/asset-id"))
|
||||
(dom/prevent-default e)))))
|
||||
|
||||
(defn on-drag-end
|
||||
[comp-inst-ref]
|
||||
(mf/use-callback
|
||||
(fn []
|
||||
(mf/set-ref-val! comp-inst-ref false))))
|
||||
|
||||
(defn on-drag-over [move-stream]
|
||||
(let [on-pointer-move (on-pointer-move move-stream)]
|
||||
(let [on-pointer-move (on-pointer-move move-stream)
|
||||
|
||||
;; Drag-over is not the same as pointer-move. Drag over is fired less frequently so we need
|
||||
;; to create a throttle so the events that cannot be processed at a certain path are
|
||||
;; discarded.
|
||||
on-pointer-move (throttle-fn 50 (fn [e] (ts/raf #(on-pointer-move e))))]
|
||||
(mf/use-callback
|
||||
(fn [e]
|
||||
(when (or (dnd/has-type? e "penpot/shape")
|
||||
|
@ -423,7 +455,7 @@
|
|||
(dom/prevent-default e))))))
|
||||
|
||||
(defn on-drop
|
||||
[file]
|
||||
[file comp-inst-ref]
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
|
@ -443,13 +475,13 @@
|
|||
(assoc :y final-y)))))
|
||||
|
||||
(dnd/has-type? event "penpot/component")
|
||||
(let [{:keys [component file-id]} (dnd/get-data event "penpot/component")
|
||||
shape (get-in component [:objects (:id component)])
|
||||
final-x (- (:x viewport-coord) (/ (:width shape) 2))
|
||||
final-y (- (:y viewport-coord) (/ (:height shape) 2))]
|
||||
(st/emit! (dwl/instantiate-component file-id
|
||||
(:id component)
|
||||
(gpt/point final-x final-y))))
|
||||
(let [event (.-nativeEvent event)
|
||||
ctrl? (kbd/ctrl? event)
|
||||
shift? (kbd/shift? event)
|
||||
alt? (kbd/alt? event)
|
||||
meta? (kbd/meta? event)]
|
||||
(st/emit! (mse/->MouseEvent :up ctrl? shift? alt? meta?))
|
||||
(mf/set-ref-val! comp-inst-ref false))
|
||||
|
||||
;; Will trigger when the user drags an image from a browser
|
||||
;; to the viewport (firefox and chrome do it a bit different
|
||||
|
@ -517,4 +549,3 @@
|
|||
(not @disable-paste)
|
||||
(not workspace-read-only?))
|
||||
(st/emit! (dw/paste-from-event event @in-viewport?)))))))
|
||||
|
||||
|
|
|
@ -62,6 +62,13 @@
|
|||
(.setData dt data-type data))
|
||||
e)))
|
||||
|
||||
(defn invisible-image
|
||||
[]
|
||||
(let [img (js/Image.)
|
||||
imd "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="]
|
||||
(set! (.-src img) imd)
|
||||
img))
|
||||
|
||||
(defn set-drag-image!
|
||||
([e image]
|
||||
(set-drag-image! e image 0 0))
|
||||
|
@ -108,11 +115,13 @@
|
|||
([e]
|
||||
(get-data e "penpot/data"))
|
||||
([e data-type]
|
||||
(let [dt (.-dataTransfer e)]
|
||||
(if (or (str/starts-with? data-type "penpot")
|
||||
(= data-type "application/json"))
|
||||
(t/decode-str (.getData dt data-type))
|
||||
(.getData dt data-type)))))
|
||||
(let [dt (.-dataTransfer e)
|
||||
data (.getData dt data-type)]
|
||||
(cond-> data
|
||||
(and (some? data) (not= data "")
|
||||
(or (str/starts-with? data-type "penpot")
|
||||
(= data-type "application/json")))
|
||||
(t/decode-str)))))
|
||||
|
||||
(defn get-files
|
||||
[e]
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require
|
||||
[beicon.v2.core :as rx]))
|
||||
|
||||
(defn- throttle-fn
|
||||
(defn throttle-fn
|
||||
[delay f]
|
||||
(let [state
|
||||
#js {:lastExecTime 0
|
||||
|
|
Loading…
Add table
Reference in a new issue