0
Fork 0
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:
alonso.torres 2024-01-30 14:55:55 +01:00
parent f75da999dc
commit 497b581576
9 changed files with 121 additions and 67 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,7 @@
(:require
[beicon.v2.core :as rx]))
(defn- throttle-fn
(defn throttle-fn
[delay f]
(let [state
#js {:lastExecTime 0