0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-16 01:31:22 -05:00

Add context menu with right click in dashboard

This commit is contained in:
Andrés Moya 2021-02-23 14:02:21 +01:00
parent 70cba4bbdf
commit e87dc6d34c
10 changed files with 235 additions and 117 deletions

View file

@ -21,6 +21,10 @@
visibility: visible; visibility: visible;
} }
.context-menu.fixed {
position: fixed;
}
.context-menu-items { .context-menu-items {
background: $color-white; background: $color-white;
border-radius: $br-small; border-radius: $br-small;

View file

@ -162,7 +162,7 @@
overflow: unset; overflow: unset;
} }
li { & > li {
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
display: flex; display: flex;

View file

@ -27,8 +27,9 @@
options (gobj/get props "options") options (gobj/get props "options")
is-selectable (gobj/get props "selectable") is-selectable (gobj/get props "selectable")
selected (gobj/get props "selected") selected (gobj/get props "selected")
top (gobj/get props "top") top (gobj/get props "top" 0)
left (gobj/get props "left") left (gobj/get props "left" 0)
fixed? (gobj/get props "fixed?" false)
offset (mf/use-state 0) offset (mf/use-state 0)
@ -36,7 +37,7 @@
(mf/use-callback (mf/use-callback
(mf/deps top @offset) (mf/deps top @offset)
(fn [node] (fn [node]
(when node (when (and node (not fixed?))
(let [{node-height :height} (dom/get-bounding-rect node) (let [{node-height :height} (dom/get-bounding-rect node)
{window-height :height} (dom/get-window-size) {window-height :height} (dom/get-window-size)
target-offset (if (> (+ top node-height) window-height) target-offset (if (> (+ top node-height) window-height)
@ -49,6 +50,7 @@
(when open? (when open?
[:> dropdown' props [:> dropdown' props
[:div.context-menu {:class (classnames :is-open open? [:div.context-menu {:class (classnames :is-open open?
:fixed fixed?
:is-selectable is-selectable) :is-selectable is-selectable)
:style {:top (+ top @offset) :style {:top (+ top @offset)
:left left}} :left left}}

View file

@ -0,0 +1,96 @@
;; 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) 2021 UXBOX Labs SL
(ns app.main.ui.dashboard.file-menu
(:require
[app.main.data.dashboard :as dd]
[app.main.data.modal :as modal]
[app.main.store :as st]
[app.main.ui.components.context-menu :refer [context-menu]]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[rumext.alpha :as mf]))
(mf/defc file-menu
[{:keys [file show? on-edit on-menu-close top left] :as props}]
(assert (some? file) "missing `file` prop")
(assert (boolean? show?) "missing `show?` prop")
(assert (fn? on-edit) "missing `on-edit` prop")
(assert (fn? on-menu-close) "missing `on-menu-close` prop")
(let [top (or top 0)
left (or left 0)
delete-fn
(mf/use-callback
(mf/deps file)
(st/emitf (dd/delete-file file)))
on-delete
(mf/use-callback
(mf/deps file)
(fn [event]
(dom/stop-propagation event)
(st/emit! (modal/show
{:type :confirm
:title (tr "modals.delete-file-confirm.title")
:message (tr "modals.delete-file-confirm.message")
:accept-label (tr "modals.delete-file-confirm.accept")
:on-accept delete-fn}))))
add-shared
(mf/use-callback
(mf/deps file)
(st/emitf (dd/set-file-shared (assoc file :is-shared true))))
del-shared
(mf/use-callback
(mf/deps file)
(st/emitf (dd/set-file-shared (assoc file :is-shared false))))
on-add-shared
(mf/use-callback
(mf/deps file)
(fn [event]
(dom/stop-propagation event)
(st/emit! (modal/show
{:type :confirm
:message ""
:title (tr "modals.add-shared-confirm.message" (:name file))
:hint (tr "modals.add-shared-confirm.hint")
:cancel-label :omit
:accept-label (tr "modals.add-shared-confirm.accept")
:accept-style :primary
:on-accept add-shared}))))
on-del-shared
(mf/use-callback
(mf/deps file)
(fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(st/emit! (modal/show
{:type :confirm
:message ""
:title (tr "modals.remove-shared-confirm.message" (:name file))
:hint (tr "modals.remove-shared-confirm.hint")
:cancel-label :omit
:accept-label (tr "modals.remove-shared-confirm.accept")
:on-accept del-shared}))))]
[:& context-menu {:on-close on-menu-close
:show show?
:fixed? (or (not= top 0) (not= left 0))
:top top
:left left
:options [[(tr "labels.rename") on-edit]
[(tr "labels.delete") on-delete]
(if (:is-shared file)
[(tr "dashboard.remove-shared") on-del-shared]
[(tr "dashboard.add-shared") on-add-shared])]}]))

View file

@ -15,6 +15,7 @@
[app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.components.context-menu :refer [context-menu]]
[app.main.ui.dashboard.grid :refer [grid]] [app.main.ui.dashboard.grid :refer [grid]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]] [app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.project-menu :refer [project-menu]]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [t]] [app.util.i18n :as i18n :refer [t]]
@ -45,23 +46,6 @@
(mf/deps project) (mf/deps project)
(st/emitf (dd/toggle-project-pin project))) (st/emitf (dd/toggle-project-pin project)))
delete-fn
(mf/use-callback
(mf/deps project)
(fn [event]
(st/emit! (dd/delete-project project)
(rt/nav :dashboard-projects {:team-id (:id team)}))))
on-delete
(mf/use-callback
(mf/deps project)
(st/emitf (modal/show
{:type :confirm
:title (t locale "modals.delete-project-confirm.title")
:message (t locale "modals.delete-project-confirm.message")
:accept-label (t locale "modals.delete-project-confirm.accept")
:on-accept delete-fn})))
on-create-clicked on-create-clicked
(mf/use-callback (mf/use-callback
(mf/deps project) (mf/deps project)
@ -81,12 +65,14 @@
(st/emit! (dd/rename-project (assoc project :name name))) (st/emit! (dd/rename-project (assoc project :name name)))
(swap! local assoc :edition false))}] (swap! local assoc :edition false))}]
[:div.dashboard-title [:div.dashboard-title
[:h1 (:name project)] [:h1 {:on-double-click on-edit}
[:div.icon {:on-click on-menu-click} i/actions] (:name project)]
[:& context-menu {:on-close on-menu-close [:div.icon {:on-click on-menu-click}
:show (:menu-open @local) i/actions]
:options [[(t locale "labels.rename") on-edit] [:& project-menu {:project project
[(t locale "labels.delete") on-delete]]}] :show? (:menu-open @local)
:on-edit on-edit
:on-menu-close on-menu-close}]
[:div.icon.pin-icon [:div.icon.pin-icon
{:class (when (:is-pinned project) "active") {:class (when (:is-pinned project) "active")
:on-click toggle-pin} :on-click toggle-pin}

View file

@ -17,6 +17,7 @@
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.components.context-menu :refer [context-menu]]
[app.main.ui.dashboard.file-menu :refer [file-menu]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]] [app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.main.worker :as wrk] [app.main.worker :as wrk]
@ -61,84 +62,36 @@
(mf/defc grid-item (mf/defc grid-item
{:wrap [mf/memo]} {:wrap [mf/memo]}
[{:keys [id file] :as props}] [{:keys [id file] :as props}]
(let [local (mf/use-state {:menu-open false :edition false}) (let [local (mf/use-state {:menu-open false
:menu-pos nil
:edition false})
locale (mf/deref i18n/locale) locale (mf/deref i18n/locale)
on-close (mf/use-callback #(swap! local assoc :menu-open false)) menu-ref (mf/use-ref)
delete-fn on-menu-close
(mf/use-callback (mf/use-callback
(mf/deps file) #(swap! local assoc :menu-open false))
(st/emitf (dd/delete-file file)))
on-delete
(mf/use-callback
(mf/deps file)
(fn [event]
(dom/stop-propagation event)
(st/emit! (modal/show
{:type :confirm
:title (t locale "modals.delete-file-confirm.title")
:message (t locale "modals.delete-file-confirm.message")
:accept-label (t locale "modals.delete-file-confirm.accept")
:on-accept delete-fn}))))
on-navigate on-navigate
(mf/use-callback (mf/use-callback
(mf/deps id) (mf/deps id)
(fn []
(let [pparams {:project-id (:project-id file)
:file-id (:id file)}
qparams {:page-id (first (get-in file [:data :pages]))}]
(st/emit! (rt/nav :workspace pparams qparams)))))
add-shared
(mf/use-callback
(mf/deps file)
(st/emitf (dd/set-file-shared (assoc file :is-shared true))))
del-shared
(mf/use-callback
(mf/deps file)
(st/emitf (dd/set-file-shared (assoc file :is-shared false))))
on-add-shared
(mf/use-callback
(mf/deps file)
(fn [event] (fn [event]
(dom/stop-propagation event) (let [menu-icon (mf/ref-val menu-ref)
(st/emit! (modal/show target (dom/get-target event)]
{:type :confirm (when-not (dom/child? target menu-icon)
:message "" (let [pparams {:project-id (:project-id file)
:title (t locale "modals.add-shared-confirm.message" (:name file)) :file-id (:id file)}
:hint (t locale "modals.add-shared-confirm.hint") qparams {:page-id (first (get-in file [:data :pages]))}]
:cancel-label :omit (st/emit! (rt/nav :workspace pparams qparams)))))))
:accept-label (t locale "modals.add-shared-confirm.accept")
:accept-style :primary
:on-accept add-shared}))))
on-del-shared
(mf/use-callback
(mf/deps file)
(fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(st/emit! (modal/show
{:type :confirm
:message ""
:title (t locale "modals.remove-shared-confirm.message" (:name file))
:hint (t locale "modals.remove-shared-confirm.hint")
:cancel-label :omit
:accept-label (t locale "modals.remove-shared-confirm.accept")
:on-accept del-shared}))))
on-menu-click on-menu-click
(mf/use-callback (mf/use-callback
(mf/deps file) (mf/deps file)
(fn [event] (fn [event]
(dom/prevent-default event) (dom/prevent-default event)
(dom/stop-propagation event) (let [position (dom/get-client-position event)]
(swap! local assoc :menu-open true))) (swap! local assoc :menu-open true
:menu-pos position))))
edit edit
(mf/use-callback (mf/use-callback
@ -154,10 +107,10 @@
(dom/stop-propagation event) (dom/stop-propagation event)
(swap! local assoc (swap! local assoc
:edition true :edition true
:menu-open false))) :menu-open false)))]
] [:div.grid-item.project-th {:on-click on-navigate
[:div.grid-item.project-th {:on-click on-navigate} :on-context-menu on-menu-click}
[:div.overlay] [:div.overlay]
[:& grid-item-thumbnail {:file file}] [:& grid-item-thumbnail {:file file}]
(when (:is-shared file) (when (:is-shared file)
@ -171,15 +124,15 @@
[:div.project-th-actions {:class (dom/classnames [:div.project-th-actions {:class (dom/classnames
:force-display (:menu-open @local))} :force-display (:menu-open @local))}
[:div.project-th-icon.menu [:div.project-th-icon.menu
{:on-click on-menu-click} {:ref menu-ref
i/actions] :on-click on-menu-click}
[:& context-menu {:on-close on-close i/actions]]
:show (:menu-open @local) [:& file-menu {:file file
:options [[(t locale "labels.rename") on-edit] :show? (:menu-open @local)
[(t locale "labels.delete") on-delete] :left (:x (:menu-pos @local))
(if (:is-shared file) :top (:y (:menu-pos @local))
[(t locale "dashboard.remove-shared") on-del-shared] :on-edit on-edit
[(t locale "dashboard.add-shared") on-add-shared])]}]]])) :on-menu-close on-menu-close}]]))
(mf/defc empty-placeholder (mf/defc empty-placeholder
[] []

View file

@ -0,0 +1,53 @@
;; 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) 2021 UXBOX Labs SL
(ns app.main.ui.dashboard.project-menu
(:require
[app.main.data.dashboard :as dd]
[app.main.data.modal :as modal]
[app.main.store :as st]
[app.main.ui.components.context-menu :refer [context-menu]]
[app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt]
[rumext.alpha :as mf]))
(mf/defc project-menu
[{:keys [project show? on-edit on-menu-close top left] :as props}]
(assert (some? project) "missing `project` prop")
(assert (boolean? show?) "missing `show?` prop")
(assert (fn? on-edit) "missing `on-edit` prop")
(assert (fn? on-menu-close) "missing `on-menu-close` prop")
(let [top (or top 0)
left (or left 0)
delete-fn
(mf/use-callback
(mf/deps project)
(fn [event]
(st/emit! (dd/delete-project project)
(rt/nav :dashboard-projects {:team-id (:team-id project)}))))
on-delete
(mf/use-callback
(mf/deps project)
(st/emitf (modal/show
{:type :confirm
:title (tr "modals.delete-project-confirm.title")
:message (tr "modals.delete-project-confirm.message")
:accept-label (tr "modals.delete-project-confirm.accept")
:on-accept delete-fn})))]
[:& context-menu {:on-close on-menu-close
:show show?
:fixed? (or (not= top 0) (not= left 0))
:top top
:left left
:options [[(tr "labels.rename") on-edit]
[(tr "labels.delete") on-delete]]}]))

View file

@ -65,6 +65,7 @@
(mf/deps project) (mf/deps project)
(st/emitf (rt/nav :dashboard-files {:team-id (:team-id project) (st/emitf (rt/nav :dashboard-files {:team-id (:team-id project)
:project-id (:id project)}))) :project-id (:id project)})))
toggle-pin toggle-pin
(mf/use-callback (mf/use-callback
(mf/deps project) (mf/deps project)
@ -88,7 +89,6 @@
params {:project-id (:id project)}] params {:project-id (:id project)}]
(st/emit! (dd/create-file (with-meta params mdata))))))] (st/emit! (dd/create-file (with-meta params mdata))))))]
[:div.dashboard-project-row {:class (when first? "first")} [:div.dashboard-project-row {:class (when first? "first")}
[:div.project [:div.project
(when-not (:is-default project) (when-not (:is-default project)

View file

@ -24,6 +24,7 @@
[app.main.ui.components.forms :as fm] [app.main.ui.components.forms :as fm]
[app.main.ui.dashboard.comments :refer [comments-section]] [app.main.ui.dashboard.comments :refer [comments-section]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]] [app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.project-menu :refer [project-menu]]
[app.main.ui.dashboard.team-form] [app.main.ui.dashboard.team-form]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.avatars :as avatars] [app.util.avatars :as avatars]
@ -42,10 +43,12 @@
(mf/defc sidebar-project (mf/defc sidebar-project
[{:keys [item selected?] :as props}] [{:keys [item selected?] :as props}]
(let [dstate (mf/deref refs/dashboard-local) (let [dstate (mf/deref refs/dashboard-local)
edit-id (:project-for-edit dstate) edit-id (:project-for-edit dstate)
edition? (mf/use-state (= (:id item) edit-id)) local (mf/use-state {:menu-open false
:menu-pos nil
:edition? (= (:id item) edit-id)})
on-click on-click
(mf/use-callback (mf/use-callback
@ -54,23 +57,41 @@
(st/emit! (rt/nav :dashboard-files {:team-id (:team-id item) (st/emit! (rt/nav :dashboard-files {:team-id (:team-id item)
:project-id (:id item)})))) :project-id (:id item)}))))
on-dbl-click on-menu-click
(mf/use-callback #(reset! edition? true)) (mf/use-callback (fn [event]
(let [position (dom/get-client-position event)]
(dom/prevent-default event)
(swap! local assoc :menu-open true
:menu-pos position))))
on-menu-close
(mf/use-callback #(swap! local assoc :menu-open false))
on-edit-open
(mf/use-callback #(swap! local assoc :edition? true))
on-edit on-edit
(mf/use-callback (mf/use-callback
(mf/deps item) (mf/deps item)
(fn [name] (fn [name]
(st/emit! (dd/rename-project (assoc item :name name))) (st/emit! (dd/rename-project (assoc item :name name)))
(reset! edition? false)))] (swap! local assoc :edition? false)))]
[:li {:on-click on-click [:*
:on-double-click on-dbl-click [:li {:on-click on-click
:class (when selected? "current")} :on-double-click on-edit-open
(if @edition? :on-context-menu on-menu-click
[:& inline-edition {:content (:name item) :class (when selected? "current")}
:on-end on-edit}] (if (:edition? @local)
[:span.element-title (:name item)])])) [:& inline-edition {:content (:name item)
:on-end on-edit}]
[:span.element-title (:name item)])]
[:& project-menu {:project item
:show? (:menu-open @local)
:left (:x (:menu-pos @local))
:top (:y (:menu-pos @local))
:on-edit on-edit-open
:on-menu-close on-menu-close}]]))
(mf/defc sidebar-search (mf/defc sidebar-search
[{:keys [search-term team-id locale] :as props}] [{:keys [search-term team-id locale] :as props}]

View file

@ -249,6 +249,9 @@
(let [class-list (.-classList ^js node)] (let [class-list (.-classList ^js node)]
(.contains ^js class-list class-name))) (.contains ^js class-list class-name)))
(defn child? [node1 node2]
(.contains ^js node2 ^js node1))
(defn get-user-agent [] (defn get-user-agent []
(.-userAgent globals/navigator)) (.-userAgent globals/navigator))