mirror of
https://github.com/penpot/penpot.git
synced 2025-05-25 18:36:12 +02:00
🎉 Add right-click context menu on workspace.
This commit is contained in:
parent
1434cb62f5
commit
cae5b5e778
9 changed files with 197 additions and 54 deletions
|
@ -1922,6 +1922,51 @@
|
||||||
query-params {:page-id (first page-ids)}]
|
query-params {:page-id (first page-ids)}]
|
||||||
(rx/of (rt/nav :workspace path-params query-params))))))
|
(rx/of (rt/nav :workspace path-params query-params))))))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Context Menu
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(s/def ::point gpt/point?)
|
||||||
|
|
||||||
|
(defn show-context-menu
|
||||||
|
[{:keys [position] :as params}]
|
||||||
|
(us/verify ::point position)
|
||||||
|
(ptk/reify ::show-context-menu
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(assoc-in state [:workspace-local :context-menu] {:position position}))))
|
||||||
|
|
||||||
|
(defn show-shape-context-menu
|
||||||
|
[{:keys [position shape] :as params}]
|
||||||
|
(us/verify ::point position)
|
||||||
|
(us/verify ::cp/minimal-shape shape)
|
||||||
|
(ptk/reify ::show-context-menu
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [selected (get-in state [:workspace-local :selected])
|
||||||
|
selected (cond
|
||||||
|
(empty? selected)
|
||||||
|
(conj selected (:id shape))
|
||||||
|
|
||||||
|
(contains? selected (:id shape))
|
||||||
|
selected
|
||||||
|
|
||||||
|
:else
|
||||||
|
#{(:id shape)})
|
||||||
|
mdata {:position position
|
||||||
|
:selected selected
|
||||||
|
:shape shape}]
|
||||||
|
(-> state
|
||||||
|
(assoc-in [:workspace-local :context-menu] mdata)
|
||||||
|
(assoc-in [:workspace-local :selected] selected))))))
|
||||||
|
|
||||||
|
(def hide-context-menu
|
||||||
|
(ptk/reify ::hide-context-menu
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(assoc-in state [:workspace-local :context-menu] nil))))
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Page Changes Reactions
|
;; Page Changes Reactions
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
|
@ -23,9 +23,11 @@
|
||||||
[{:keys [shape] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
(let [selected (mf/deref refs/selected-shapes)
|
(let [selected (mf/deref refs/selected-shapes)
|
||||||
selected? (contains? selected (:id shape))
|
selected? (contains? selected (:id shape))
|
||||||
on-mouse-down #(common/on-mouse-down % shape selected)]
|
on-mouse-down #(common/on-mouse-down % shape)
|
||||||
|
on-context-menu #(common/on-context-menu % shape)]
|
||||||
[:g.shape {:class (when selected? "selected")
|
[:g.shape {:class (when selected? "selected")
|
||||||
:on-mouse-down on-mouse-down}
|
:on-mouse-down on-mouse-down
|
||||||
|
:on-context-menu on-context-menu}
|
||||||
[:& circle-shape {:shape shape}]]))
|
[:& circle-shape {:shape shape}]]))
|
||||||
|
|
||||||
;; --- Circle Shape
|
;; --- Circle Shape
|
||||||
|
|
|
@ -68,33 +68,46 @@
|
||||||
([event {:keys [id type] :as shape} kk-tmp]
|
([event {:keys [id type] :as shape} kk-tmp]
|
||||||
(let [selected @refs/selected-shapes
|
(let [selected @refs/selected-shapes
|
||||||
selected? (contains? selected id)
|
selected? (contains? selected id)
|
||||||
drawing? @refs/selected-drawing-tool]
|
drawing? @refs/selected-drawing-tool
|
||||||
(when-not (:blocked shape)
|
button (.-which (.-nativeEvent event))]
|
||||||
(cond
|
(when-not (:blocked shape)
|
||||||
drawing?
|
(cond
|
||||||
nil
|
(not= 1 button)
|
||||||
|
nil
|
||||||
|
|
||||||
(= type :frame)
|
drawing?
|
||||||
(when selected?
|
nil
|
||||||
(dom/stop-propagation event)
|
|
||||||
(st/emit! start-move-frame))
|
|
||||||
|
|
||||||
(and (not selected?) (empty? selected))
|
(= type :frame)
|
||||||
(do
|
(when selected?
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(st/emit! dw/deselect-all
|
(st/emit! start-move-frame))
|
||||||
(dw/select-shape id)
|
|
||||||
start-move-selected))
|
|
||||||
|
|
||||||
(and (not selected?) (not (empty? selected)))
|
(and (not selected?) (empty? selected))
|
||||||
(do
|
(do
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(if (kbd/shift? event)
|
(st/emit! dw/deselect-all
|
||||||
(st/emit! (dw/select-shape id))
|
(dw/select-shape id)
|
||||||
(st/emit! dw/deselect-all
|
start-move-selected))
|
||||||
(dw/select-shape id)
|
|
||||||
start-move-selected)))
|
(and (not selected?) (not (empty? selected)))
|
||||||
:else
|
(do
|
||||||
(do
|
(dom/stop-propagation event)
|
||||||
(dom/stop-propagation event)
|
(if (kbd/shift? event)
|
||||||
(st/emit! start-move-selected)))))))
|
(st/emit! (dw/select-shape id))
|
||||||
|
(st/emit! dw/deselect-all
|
||||||
|
(dw/select-shape id)
|
||||||
|
start-move-selected)))
|
||||||
|
:else
|
||||||
|
(do
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(st/emit! start-move-selected)))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn on-context-menu
|
||||||
|
[event shape]
|
||||||
|
(dom/prevent-default event)
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(let [position (dom/get-client-position event)]
|
||||||
|
(st/emit!(dw/show-shape-context-menu {:position position
|
||||||
|
:shape shape}))))
|
||||||
|
|
|
@ -95,6 +95,7 @@
|
||||||
:deps (mf/deps (:id shape))})
|
:deps (mf/deps (:id shape))})
|
||||||
selected? (mf/deref selected-iref)
|
selected? (mf/deref selected-iref)
|
||||||
on-mouse-down #(common/on-mouse-down % shape)
|
on-mouse-down #(common/on-mouse-down % shape)
|
||||||
|
on-context-menu #(common/on-context-menu % shape)
|
||||||
shape (merge frame-default-props shape)
|
shape (merge frame-default-props shape)
|
||||||
|
|
||||||
childs (mapv #(get objects %) (:shapes shape))
|
childs (mapv #(get objects %) (:shapes shape))
|
||||||
|
@ -105,6 +106,7 @@
|
||||||
(st/emit! dw/deselect-all
|
(st/emit! dw/deselect-all
|
||||||
(dw/select-shape (:id shape))))]
|
(dw/select-shape (:id shape))))]
|
||||||
[:g {:class (when selected? "selected")
|
[:g {:class (when selected? "selected")
|
||||||
|
:on-context-menu on-context-menu
|
||||||
:on-double-click on-double-click
|
:on-double-click on-double-click
|
||||||
:on-mouse-down on-mouse-down}
|
:on-mouse-down on-mouse-down}
|
||||||
[:& frame-shape {:shape shape :childs childs}]])))
|
[:& frame-shape {:shape shape :childs childs}]])))
|
||||||
|
|
|
@ -23,8 +23,10 @@
|
||||||
(mf/defrc rect-wrapper
|
(mf/defrc rect-wrapper
|
||||||
[props]
|
[props]
|
||||||
(let [shape (unchecked-get props "shape")
|
(let [shape (unchecked-get props "shape")
|
||||||
on-mouse-down #(common/on-mouse-down % shape)]
|
on-mouse-down #(common/on-mouse-down % shape)
|
||||||
[:g.shape {:on-mouse-down on-mouse-down}
|
on-context-menu #(common/on-context-menu % shape)]
|
||||||
|
[:g.shape {:on-mouse-down on-mouse-down
|
||||||
|
:on-context-menu on-context-menu}
|
||||||
[:& rect-shape {:shape shape}]]))
|
[:& rect-shape {:shape shape}]]))
|
||||||
|
|
||||||
;; --- Rect Shape
|
;; --- Rect Shape
|
||||||
|
|
|
@ -22,9 +22,8 @@
|
||||||
[uxbox.main.ui.messages :refer [messages-widget]]
|
[uxbox.main.ui.messages :refer [messages-widget]]
|
||||||
[uxbox.main.ui.workspace.viewport :refer [viewport]]
|
[uxbox.main.ui.workspace.viewport :refer [viewport]]
|
||||||
[uxbox.main.ui.workspace.colorpalette :refer [colorpalette]]
|
[uxbox.main.ui.workspace.colorpalette :refer [colorpalette]]
|
||||||
;; [uxbox.main.ui.workspace.download]
|
[uxbox.main.ui.workspace.context-menu :refer [context-menu]]
|
||||||
[uxbox.main.ui.workspace.header :refer [header]]
|
[uxbox.main.ui.workspace.header :refer [header]]
|
||||||
;; [uxbox.main.ui.workspace.images]
|
|
||||||
[uxbox.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]]
|
[uxbox.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]]
|
||||||
[uxbox.main.ui.workspace.scroll :as scroll]
|
[uxbox.main.ui.workspace.scroll :as scroll]
|
||||||
[uxbox.main.ui.workspace.shortcuts :as shortcuts]
|
[uxbox.main.ui.workspace.shortcuts :as shortcuts]
|
||||||
|
@ -74,6 +73,7 @@
|
||||||
[:& colorpalette])
|
[:& colorpalette])
|
||||||
|
|
||||||
[:main.main-content
|
[:main.main-content
|
||||||
|
[:& context-menu {}]
|
||||||
[:section.workspace-content
|
[:section.workspace-content
|
||||||
{:class classes
|
{:class classes
|
||||||
:on-scroll on-scroll
|
:on-scroll on-scroll
|
||||||
|
|
72
frontend/src/uxbox/main/ui/workspace/context_menu.cljs
Normal file
72
frontend/src/uxbox/main/ui/workspace/context_menu.cljs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) 2020 UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns uxbox.main.ui.workspace.context-menu
|
||||||
|
"A workspace specific context menu (mouse right click)."
|
||||||
|
(:require
|
||||||
|
[beicon.core :as rx]
|
||||||
|
[lentes.core :as l]
|
||||||
|
[potok.core :as ptk]
|
||||||
|
[rumext.alpha :as mf]
|
||||||
|
[uxbox.main.store :as st]
|
||||||
|
[uxbox.main.refs :as refs]
|
||||||
|
[uxbox.main.streams :as ms]
|
||||||
|
[uxbox.builtins.icons :as i]
|
||||||
|
[uxbox.util.dom :as dom]
|
||||||
|
[uxbox.main.data.workspace :as dw]
|
||||||
|
[uxbox.main.ui.react-hooks :refer [use-rxsub]]
|
||||||
|
[uxbox.main.ui.components.dropdown :refer [dropdown]]))
|
||||||
|
|
||||||
|
(def menu-ref
|
||||||
|
(-> (l/key :context-menu)
|
||||||
|
(l/derive refs/workspace-local)))
|
||||||
|
|
||||||
|
(defn- prevent-default
|
||||||
|
[event]
|
||||||
|
(dom/prevent-default event)
|
||||||
|
(dom/stop-propagation event))
|
||||||
|
|
||||||
|
(mf/defc shape-context-menu
|
||||||
|
[{:keys [mdata] :as props}]
|
||||||
|
(let [shape (:shape mdata)
|
||||||
|
selected (:selected mdata)
|
||||||
|
|
||||||
|
on-duplicate
|
||||||
|
(fn [event]
|
||||||
|
(st/emit! dw/duplicate-selected))
|
||||||
|
|
||||||
|
on-delete
|
||||||
|
(fn [event]
|
||||||
|
(st/emit! dw/delete-selected)) ]
|
||||||
|
[:*
|
||||||
|
[:li {:on-click on-duplicate} i/copy [:span "duplicate"]]
|
||||||
|
[:li {:on-click on-delete} i/trash [:span "delete"]]]))
|
||||||
|
|
||||||
|
(mf/defc viewport-context-menu
|
||||||
|
[{:keys [mdata] :as props}]
|
||||||
|
[:*
|
||||||
|
[:li i/copy [:span "paste (TODO)"]]
|
||||||
|
[:li i/copy [:span "copy as svg (TODO)"]]])
|
||||||
|
|
||||||
|
(mf/defc context-menu
|
||||||
|
[props]
|
||||||
|
(let [mdata (mf/deref menu-ref)]
|
||||||
|
[:& dropdown {:show (boolean mdata)
|
||||||
|
:on-close #(st/emit! dw/hide-context-menu)}
|
||||||
|
[:ul.workspace-context-menu
|
||||||
|
{:style {:top (- (get-in mdata [:position :y]) 20)
|
||||||
|
:left (get-in mdata [:position :x])}
|
||||||
|
:on-context-menu prevent-default}
|
||||||
|
|
||||||
|
(if (:shape mdata)
|
||||||
|
[:& shape-context-menu {:mdata mdata}]
|
||||||
|
[:& viewport-context-menu {:mdata mdata}])]]))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -118,6 +118,14 @@
|
||||||
(st/emit! dw/deselect-all
|
(st/emit! dw/deselect-all
|
||||||
(dw/select-shape id)))))
|
(dw/select-shape id)))))
|
||||||
|
|
||||||
|
on-context-menu
|
||||||
|
(fn [event]
|
||||||
|
(dom/prevent-default event)
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(let [pos (dom/get-client-position event)]
|
||||||
|
(st/emit! (dw/show-shape-context-menu {:position pos
|
||||||
|
:shape item}))))
|
||||||
|
|
||||||
on-hover
|
on-hover
|
||||||
(fn [item monitor]
|
(fn [item monitor]
|
||||||
(st/emit! (dw/shape-order-change (:obj-id item) index)))
|
(st/emit! (dw/shape-order-change (:obj-id item) index)))
|
||||||
|
@ -134,6 +142,7 @@
|
||||||
:on-hover on-hover
|
:on-hover on-hover
|
||||||
:on-drop on-drop})]
|
:on-drop on-drop})]
|
||||||
[:li {:ref dnd-ref
|
[:li {:ref dnd-ref
|
||||||
|
:on-context-menu on-context-menu
|
||||||
:class (dom/classnames
|
:class (dom/classnames
|
||||||
:selected selected?
|
:selected selected?
|
||||||
:dragging-TODO (:dragging? dprops))}
|
:dragging-TODO (:dragging? dprops))}
|
||||||
|
@ -195,6 +204,14 @@
|
||||||
(st/emit! dw/deselect-all
|
(st/emit! dw/deselect-all
|
||||||
(dw/select-shape id)))))
|
(dw/select-shape id)))))
|
||||||
|
|
||||||
|
on-context-menu
|
||||||
|
(fn [event]
|
||||||
|
(dom/prevent-default event)
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(let [pos (dom/get-client-position event)]
|
||||||
|
(st/emit! (dw/show-shape-context-menu {:position pos
|
||||||
|
:shape item}))))
|
||||||
|
|
||||||
on-drop
|
on-drop
|
||||||
(fn [item monitor]
|
(fn [item monitor]
|
||||||
(st/emit! (dw/commit-shape-order-change (:obj-id item))))
|
(st/emit! (dw/commit-shape-order-change (:obj-id item))))
|
||||||
|
@ -211,6 +228,7 @@
|
||||||
:on-hover on-hover
|
:on-hover on-hover
|
||||||
:on-drop on-drop})]
|
:on-drop on-drop})]
|
||||||
[:li.group {:ref dnd-ref
|
[:li.group {:ref dnd-ref
|
||||||
|
:on-context-menu on-context-menu
|
||||||
:class (dom/classnames
|
:class (dom/classnames
|
||||||
:selected selected?
|
:selected selected?
|
||||||
:dragging-TODO (:dragging? dprops))}
|
:dragging-TODO (:dragging? dprops))}
|
||||||
|
|
|
@ -165,11 +165,8 @@
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(let [ctrl? (kbd/ctrl? event)
|
(let [position (dom/get-client-position event)]
|
||||||
shift? (kbd/shift? event)
|
(st/emit! (dw/show-context-menu {:position position}))))
|
||||||
opts {:shift? shift?
|
|
||||||
:ctrl? ctrl?}]
|
|
||||||
(st/emit! (ms/->MouseEvent :context-menu ctrl? shift?))))
|
|
||||||
|
|
||||||
on-mouse-up
|
on-mouse-up
|
||||||
(fn [event]
|
(fn [event]
|
||||||
|
@ -225,25 +222,14 @@
|
||||||
(st/emit! ::finish-positioning #_(dw/stop-viewport-positioning)))
|
(st/emit! ::finish-positioning #_(dw/stop-viewport-positioning)))
|
||||||
(st/emit! (ms/->KeyboardEvent :up key ctrl? shift?))))
|
(st/emit! (ms/->KeyboardEvent :up key ctrl? shift?))))
|
||||||
|
|
||||||
;; translate-point-to-viewport
|
|
||||||
;; (fn [pt]
|
|
||||||
;; (let [viewport (mf/ref-node viewport-ref)
|
|
||||||
;; brect (.getBoundingClientRect viewport)
|
|
||||||
;; brect (gpt/point (parse-int (.-left brect))
|
|
||||||
;; (parse-int (.-top brect)))]
|
|
||||||
;; (gpt/subtract pt brect)))
|
|
||||||
|
|
||||||
on-mouse-move
|
on-mouse-move
|
||||||
(fn [event]
|
(fn [event]
|
||||||
;; NOTE: offsetX and offsetY are marked as "experimental" on
|
;; NOTE: offsetX and offsetY are marked as "experimental" on
|
||||||
;; MDN site but seems like they are supported on all
|
;; MDN site but seems like they are supported on all
|
||||||
;; browsers so we can avoid translation opetation just using
|
;; browsers so we can avoid translation opetation just using
|
||||||
;; this attributes.
|
;; this attributes.
|
||||||
(let [;; pt (gpt/point (.-clientX event)
|
(let [pt (dom/get-offset-position event)]
|
||||||
;; (.-clientY event))
|
(reset! last-position pt)
|
||||||
;; pt (translate-point-to-viewport pt)
|
|
||||||
pt (gpt/point (.-offsetX (.-nativeEvent event))
|
|
||||||
(.-offsetY (.-nativeEvent event)))]
|
|
||||||
(st/emit! (ms/->PointerEvent :viewport pt
|
(st/emit! (ms/->PointerEvent :viewport pt
|
||||||
(kbd/ctrl? event)
|
(kbd/ctrl? event)
|
||||||
(kbd/shift? event)))))
|
(kbd/shift? event)))))
|
||||||
|
@ -251,10 +237,14 @@
|
||||||
on-mount
|
on-mount
|
||||||
(fn []
|
(fn []
|
||||||
(let [key1 (events/listen js/document EventType.KEYDOWN on-key-down)
|
(let [key1 (events/listen js/document EventType.KEYDOWN on-key-down)
|
||||||
key2 (events/listen js/document EventType.KEYUP on-key-up)]
|
key2 (events/listen js/document EventType.KEYUP on-key-up)
|
||||||
|
dnode (mf/ref-val viewport-ref)
|
||||||
|
key3 (events/listen dnode EventType.MOUSEMOVE on-mouse-move)]
|
||||||
(fn []
|
(fn []
|
||||||
(events/unlistenByKey key1)
|
(events/unlistenByKey key1)
|
||||||
(events/unlistenByKey key2))))]
|
(events/unlistenByKey key2)
|
||||||
|
(events/unlistenByKey key3)
|
||||||
|
)))]
|
||||||
|
|
||||||
(mf/use-effect on-mount)
|
(mf/use-effect on-mount)
|
||||||
[:*
|
[:*
|
||||||
|
@ -266,7 +256,6 @@
|
||||||
:on-context-menu on-context-menu
|
:on-context-menu on-context-menu
|
||||||
:on-click on-click
|
:on-click on-click
|
||||||
:on-double-click on-double-click
|
:on-double-click on-double-click
|
||||||
:on-mouse-move on-mouse-move
|
|
||||||
:on-mouse-down on-mouse-down
|
:on-mouse-down on-mouse-down
|
||||||
:on-mouse-up on-mouse-up}
|
:on-mouse-up on-mouse-up}
|
||||||
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
|
[:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue