;; 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/. ;; ;; Copyright (c) KALEIDOS INC (ns app.main.ui.workspace.context-menu "A workspace specific context menu (mouse right click)." (:require-macros [app.main.style :as stl]) (:require [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] [app.common.types.component :as ctk] [app.common.types.container :as ctn] [app.common.types.page :as ctp] [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.main.data.events :as ev] [app.main.data.modal :as modal] [app.main.data.shortcuts :as scd] [app.main.data.workspace :as dw] [app.main.data.workspace.interactions :as dwi] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shortcuts :as sc] [app.main.features :as features] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.shape-icon :as sic] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.assets.common :as cmm] [app.util.dom :as dom] [app.util.i18n :refer [tr] :as i18n] [app.util.timers :as timers] [okulary.core :as l] [rumext.v2 :as mf])) (def menu-ref (l/derived :context-menu refs/workspace-local)) (defn- prevent-default [event] (dom/prevent-default event) (dom/stop-propagation event)) (mf/defc menu-entry {::mf/props :obj} [{:keys [title shortcut on-click on-pointer-enter on-pointer-leave on-unmount children selected? icon disabled value]}] (let [submenu-ref (mf/use-ref nil) hovering? (mf/use-ref false) on-pointer-enter (mf/use-callback (fn [] (mf/set-ref-val! hovering? true) (let [submenu-node (mf/ref-val submenu-ref)] (when (some? submenu-node) (dom/set-css-property! submenu-node "display" "block"))) (when on-pointer-enter (on-pointer-enter)))) on-pointer-leave (mf/use-callback (fn [] (mf/set-ref-val! hovering? false) (let [submenu-node (mf/ref-val submenu-ref)] (when (some? submenu-node) (timers/schedule 200 #(when-not (mf/ref-val hovering?) (dom/set-css-property! submenu-node "display" "none"))))) (when on-pointer-leave (on-pointer-leave)))) set-dom-node (mf/use-callback (fn [dom] (let [submenu-node (mf/ref-val submenu-ref)] (when (and (some? dom) (some? submenu-node)) (dom/set-css-property! submenu-node "top" (str (.-offsetTop dom) "px"))))))] (mf/use-effect (mf/deps on-unmount) (constantly on-unmount)) (if icon [:li {:class (stl/css :icon-menu-item) :disabled disabled :data-value value :ref set-dom-node :on-click on-click :on-pointer-enter on-pointer-enter :on-pointer-leave on-pointer-leave} [:span {:class (stl/css :icon-wrapper)} (if selected? [:span {:class (stl/css :selected-icon)} i/tick] [:span {:class (stl/css :selected-icon)}]) [:span {:class (stl/css :shape-icon)} icon]] [:span {:class (stl/css :title)} title]] [:li {:class (stl/css :context-menu-item) :disabled disabled :ref set-dom-node :data-value value :on-click on-click :on-pointer-enter on-pointer-enter :on-pointer-leave on-pointer-leave} [:span {:class (stl/css :title)} title] (when shortcut [:span {:class (stl/css :shortcut)} (for [[idx sc] (d/enumerate (scd/split-sc shortcut))] [:span {:key (dm/str shortcut "-" idx) :class (stl/css :shortcut-key)} sc])]) (when (> (count children) 1) [:span {:class (stl/css :submenu-icon)} i/arrow]) (when (> (count children) 1) [:ul {:class (stl/css :workspace-context-submenu) :ref submenu-ref :style {:display "none" :left 250} :on-context-menu prevent-default} children])]))) (mf/defc menu-separator [] [:li {:class (stl/css :separator)}]) (mf/defc context-menu-edit [_] (let [do-copy #(st/emit! (dw/copy-selected)) do-cut #(st/emit! (dw/copy-selected) (dw/delete-selected)) do-paste #(st/emit! (dw/paste-from-clipboard)) do-duplicate #(st/emit! (dw/duplicate-selected true))] [:* [:& menu-entry {:title (tr "workspace.shape.menu.copy") :shortcut (sc/get-tooltip :copy) :on-click do-copy}] [:& menu-entry {:title (tr "workspace.shape.menu.cut") :shortcut (sc/get-tooltip :cut) :on-click do-cut}] [:& menu-entry {:title (tr "workspace.shape.menu.paste") :shortcut (sc/get-tooltip :paste) :on-click do-paste}] [:& menu-entry {:title (tr "workspace.shape.menu.duplicate") :shortcut (sc/get-tooltip :duplicate) :on-click do-duplicate}] [:& menu-separator]])) (mf/defc context-menu-layer-position [{:keys [shapes]}] (let [do-bring-forward (mf/use-fn #(st/emit! (dw/vertical-order-selected :up))) do-bring-to-front (mf/use-fn #(st/emit! (dw/vertical-order-selected :top))) do-send-backward (mf/use-fn #(st/emit! (dw/vertical-order-selected :down))) do-send-to-back (mf/use-fn #(st/emit! (dw/vertical-order-selected :bottom))) select-shapes (fn [id] #(st/emit! (dws/select-shape id))) on-pointer-enter (fn [id] #(st/emit! (dw/highlight-shape id))) on-pointer-leave (fn [id] #(st/emit! (dw/dehighlight-shape id))) on-unmount (fn [id] #(st/emit! (dw/dehighlight-shape id))) ;; NOTE: we use deref instead of mf/deref on objects because ;; we really don't want rerender on object changes hover-ids (deref refs/current-hover-ids) objects (deref refs/workspace-page-objects) hover-objs (into [] (keep (d/getf objects)) hover-ids)] [:* (when (> (count hover-objs) 1) [:& menu-entry {:title (tr "workspace.shape.menu.select-layer")} (for [object hover-objs] [:& menu-entry {:title (:name object) :key (dm/str (:id object)) :selected? (some #(= object %) shapes) :on-click (select-shapes (:id object)) :on-pointer-enter (on-pointer-enter (:id object)) :on-pointer-leave (on-pointer-leave (:id object)) :on-unmount (on-unmount (:id object)) :icon (sic/element-icon {:shape object})}])]) [:& menu-entry {:title (tr "workspace.shape.menu.forward") :shortcut (sc/get-tooltip :bring-forward) :on-click do-bring-forward}] [:& menu-entry {:title (tr "workspace.shape.menu.front") :shortcut (sc/get-tooltip :bring-front) :on-click do-bring-to-front}] [:& menu-entry {:title (tr "workspace.shape.menu.backward") :shortcut (sc/get-tooltip :bring-backward) :on-click do-send-backward}] [:& menu-entry {:title (tr "workspace.shape.menu.back") :shortcut (sc/get-tooltip :bring-back) :on-click do-send-to-back}] [:& menu-separator]])) (mf/defc context-menu-flip [] (let [do-flip-vertical #(st/emit! (dw/flip-vertical-selected)) do-flip-horizontal #(st/emit! (dw/flip-horizontal-selected))] [:* [:& menu-entry {:title (tr "workspace.shape.menu.flip-vertical") :shortcut (sc/get-tooltip :flip-vertical) :on-click do-flip-vertical}] [:& menu-entry {:title (tr "workspace.shape.menu.flip-horizontal") :shortcut (sc/get-tooltip :flip-horizontal) :on-click do-flip-horizontal}] [:& menu-separator]])) (mf/defc context-menu-thumbnail [{:keys [shapes]}] (let [single? (= (count shapes) 1) has-frame? (some cfh/frame-shape? shapes) do-toggle-thumbnail #(st/emit! (dw/toggle-file-thumbnail-selected))] (when (and single? has-frame?) [:* (if (every? :use-for-thumbnail shapes) [:& menu-entry {:title (tr "workspace.shape.menu.thumbnail-remove") :on-click do-toggle-thumbnail}] [:& menu-entry {:title (tr "workspace.shape.menu.thumbnail-set") :shortcut (sc/get-tooltip :thumbnail-set) :on-click do-toggle-thumbnail}]) [:& menu-separator]]))) (mf/defc context-menu-group [{:keys [shapes]}] (let [multiple? (> (count shapes) 1) single? (= (count shapes) 1) objects (deref refs/workspace-page-objects) any-in-copy? (some true? (map #(ctn/has-any-copy-parent? objects %) shapes)) ;; components can't be ungrouped has-frame? (->> shapes (d/seek #(and (cfh/frame-shape? %) (not (ctk/instance-head? %))))) has-group? (->> shapes (d/seek #(and (cfh/group-shape? %) (not (ctk/instance-head? %))))) has-bool? (->> shapes (d/seek cfh/bool-shape?)) has-mask? (->> shapes (d/seek :masked-group)) is-group? (and single? has-group?) is-bool? (and single? has-bool?) do-create-group #(st/emit! (dw/group-selected)) do-remove-group #(st/emit! (dw/ungroup-selected)) do-mask-group #(st/emit! (dw/mask-group)) do-unmask-group #(st/emit! (dw/unmask-group)) do-create-artboard-from-selection #(st/emit! (dwsh/create-artboard-from-selection))] [:* (when (not any-in-copy?) [:* (when (or has-bool? has-group? has-mask? has-frame?) [:& menu-entry {:title (tr "workspace.shape.menu.ungroup") :shortcut (sc/get-tooltip :ungroup) :on-click do-remove-group}]) [:& menu-entry {:title (tr "workspace.shape.menu.group") :shortcut (sc/get-tooltip :group) :on-click do-create-group}] (when (or multiple? (and is-group? (not has-mask?)) is-bool?) [:& menu-entry {:title (tr "workspace.shape.menu.mask") :shortcut (sc/get-tooltip :mask) :on-click do-mask-group}]) (when has-mask? [:& menu-entry {:title (tr "workspace.shape.menu.unmask") :shortcut (sc/get-tooltip :unmask) :on-click do-unmask-group}]) [:& menu-entry {:title (tr "workspace.shape.menu.create-artboard-from-selection") :shortcut (sc/get-tooltip :artboard-selection) :on-click do-create-artboard-from-selection}] [:& menu-separator]])])) (mf/defc context-focus-mode-menu [{:keys []}] (let [focus (mf/deref refs/workspace-focus-selected) do-toggle-focus-mode #(st/emit! (dw/toggle-focus-mode))] [:& menu-entry {:title (if (empty? focus) (tr "workspace.focus.focus-on") (tr "workspace.focus.focus-off")) :shortcut (sc/get-tooltip :toggle-focus-mode) :on-click do-toggle-focus-mode}])) (mf/defc context-menu-path [{:keys [shapes disable-flatten? disable-booleans?]}] (let [multiple? (> (count shapes) 1) single? (= (count shapes) 1) has-group? (->> shapes (d/seek cfh/group-shape?)) has-bool? (->> shapes (d/seek cfh/bool-shape?)) has-frame? (->> shapes (d/seek cfh/frame-shape?)) has-path? (->> shapes (d/seek cfh/path-shape?)) is-group? (and single? has-group?) is-bool? (and single? has-bool?) is-frame? (and single? has-frame?) do-start-editing (fn [] (timers/schedule #(st/emit! (dw/start-editing-selected)))) do-transform-to-path #(st/emit! (dw/convert-selected-to-path)) make-do-bool (fn [bool-type] #(cond multiple? (st/emit! (dw/create-bool bool-type)) is-group? (st/emit! (dw/group-to-bool (-> shapes first :id) bool-type)) is-bool? (st/emit! (dw/change-bool-type (-> shapes first :id) bool-type))))] [:* (when (and single? (not is-frame?)) [:& menu-entry {:title (tr "workspace.shape.menu.edit") :shortcut (sc/get-tooltip :start-editing) :on-click do-start-editing}]) (when-not (or disable-flatten? has-frame? has-path?) [:& menu-entry {:title (tr "workspace.shape.menu.transform-to-path") :on-click do-transform-to-path}]) (when (and (not disable-booleans?) (or multiple? (and single? (or is-group? is-bool?)))) [:& menu-entry {:title (tr "workspace.shape.menu.path")} [:& menu-entry {:title (tr "workspace.shape.menu.union") :shortcut (sc/get-tooltip :bool-union) :on-click (make-do-bool :union)}] [:& menu-entry {:title (tr "workspace.shape.menu.difference") :shortcut (sc/get-tooltip :bool-difference) :on-click (make-do-bool :difference)}] [:& menu-entry {:title (tr "workspace.shape.menu.intersection") :shortcut (sc/get-tooltip :bool-intersection) :on-click (make-do-bool :intersection)}] [:& menu-entry {:title (tr "workspace.shape.menu.exclude") :shortcut (sc/get-tooltip :bool-exclude) :on-click (make-do-bool :exclude)}] (when (and single? is-bool? (not disable-flatten?)) [:* [:& menu-separator] [:& menu-entry {:title (tr "workspace.shape.menu.flatten") :on-click do-transform-to-path}]])])])) (mf/defc context-menu-layer-options [{:keys [shapes]}] (let [ids (mapv :id shapes) do-show-shape #(st/emit! (dw/update-shape-flags ids {:hidden false})) do-hide-shape #(st/emit! (dw/update-shape-flags ids {:hidden true})) do-lock-shape #(st/emit! (dw/update-shape-flags ids {:blocked true})) do-unlock-shape #(st/emit! (dw/update-shape-flags ids {:blocked false}))] [:* (if (every? :hidden shapes) [:& menu-entry {:title (tr "workspace.shape.menu.show") :shortcut (sc/get-tooltip :toggle-visibility) :on-click do-show-shape}] [:& menu-entry {:title (tr "workspace.shape.menu.hide") :shortcut (sc/get-tooltip :toggle-visibility) :on-click do-hide-shape}]) (if (every? :blocked shapes) [:& menu-entry {:title (tr "workspace.shape.menu.unlock") :shortcut (sc/get-tooltip :toggle-lock) :on-click do-unlock-shape}] [:& menu-entry {:title (tr "workspace.shape.menu.lock") :shortcut (sc/get-tooltip :toggle-lock) :on-click do-lock-shape}])])) (mf/defc context-menu-prototype [{:keys [shapes]}] (let [options (mf/deref refs/workspace-page-options) options-mode (mf/deref refs/options-mode-global) do-add-flow #(st/emit! (dwi/add-flow-selected-frame)) do-remove-flow #(st/emit! (dwi/remove-flow (:id %))) flows (:flows options) prototype? (= options-mode :prototype) single? (= (count shapes) 1) has-frame? (->> shapes (d/seek cfh/frame-shape?)) is-frame? (and single? has-frame?)] (when (and prototype? is-frame?) (let [flow (ctp/get-frame-flow flows (-> shapes first :id))] (if (some? flow) [:& menu-entry {:title (tr "workspace.shape.menu.delete-flow-start") :on-click (do-remove-flow flow)}] [:& menu-entry {:title (tr "workspace.shape.menu.flow-start") :on-click do-add-flow}]))))) (mf/defc context-menu-layout {::mf/props :obj} [{:keys [shapes]}] (let [single? (= (count shapes) 1) objects (deref refs/workspace-page-objects) any-in-copy? (some true? (map #(ctn/has-any-copy-parent? objects %) shapes)) has-flex? (and single? (every? ctl/flex-layout? shapes)) has-grid? (and single? (every? ctl/grid-layout? shapes)) on-add-layout (mf/use-fn (fn [event] (let [type (-> (dom/get-current-target event) (dom/get-data "value") (keyword))] (st/emit! (with-meta (dwsl/create-layout type) {::ev/origin "workspace:context-menu"}))))) on-remove-layout (mf/use-fn (mf/deps shapes) (fn [_event] (let [ids (map :id shapes)] (st/emit! (dwsl/remove-layout ids)))))] [:* (when (not any-in-copy?) (if (or ^boolean has-flex? ^boolean has-grid?) [:div [:& menu-separator] (if has-flex? [:& menu-entry {:title (tr "workspace.shape.menu.remove-flex") :shortcut (sc/get-tooltip :toggle-layout-flex) :on-click on-remove-layout}] [:& menu-entry {:title (tr "workspace.shape.menu.remove-grid") :shortcut (sc/get-tooltip :toggle-layout-grid) :on-click on-remove-layout}])] [:div [:& menu-separator] [:& menu-entry {:title (tr "workspace.shape.menu.add-flex") :shortcut (sc/get-tooltip :toggle-layout-flex) :value "flex" :on-click on-add-layout}] [:& menu-entry {:title (tr "workspace.shape.menu.add-grid") :shortcut (sc/get-tooltip :toggle-layout-grid) :value "grid" :on-click on-add-layout}]]))])) (mf/defc context-menu-component [{:keys [shapes]}] (let [components-v2 (features/use-feature "components/v2") single? (= (count shapes) 1) objects (deref refs/workspace-page-objects) can-make-component (every? true? (map #(ctn/valid-shape-for-component? objects %) shapes)) heads (filter ctk/instance-head? shapes) components-menu-entries (cmm/generate-components-menu-entries heads components-v2) do-add-component #(st/emit! (dwl/add-component)) do-add-multiple-components #(st/emit! (dwl/add-multiple-components))] [:* (when can-make-component ;; We don't want to change the structure of component copies [:* [:& menu-separator] [:& menu-entry {:title (tr "workspace.shape.menu.create-component") :shortcut (sc/get-tooltip :create-component) :on-click do-add-component}] (when (not single?) [:& menu-entry {:title (tr "workspace.shape.menu.create-multiple-components") :on-click do-add-multiple-components}])]) (when (seq components-menu-entries) [:* [:& menu-separator] (for [entry components-menu-entries :when (not (nil? entry))] [:& menu-entry {:key (uuid/next) :title (tr (:msg entry)) :shortcut (when (contains? entry :shortcut) (sc/get-tooltip (:shortcut entry))) :on-click (:action entry)}])])])) (mf/defc context-menu-delete [] (let [do-delete #(st/emit! (dw/delete-selected))] [:* [:& menu-separator] [:& menu-entry {:title (tr "workspace.shape.menu.delete") :shortcut (sc/get-tooltip :delete) :on-click do-delete}]])) (mf/defc shape-context-menu {::mf/wrap [mf/memo]} [{:keys [mdata] :as props}] (let [{:keys [disable-booleans? disable-flatten?]} mdata shapes (mf/deref refs/selected-objects) props #js {:shapes shapes :disable-booleans? disable-booleans? :disable-flatten? disable-flatten?}] (when-not (empty? shapes) [:* [:> context-menu-edit props] [:> context-menu-layer-position props] [:> context-menu-flip props] [:> context-menu-thumbnail props] [:> context-menu-group props] [:> context-focus-mode-menu props] [:> context-menu-path props] [:> context-menu-layer-options props] [:> context-menu-prototype props] [:> context-menu-layout props] [:> context-menu-component props] [:> context-menu-delete props]]))) (mf/defc page-item-context-menu [{:keys [mdata] :as props}] (let [page (:page mdata) deletable? (:deletable? mdata) id (:id page) delete-fn #(st/emit! (dw/delete-page id)) do-delete #(st/emit! (modal/show {:type :confirm :title (tr "modals.delete-page.title") :message (tr "modals.delete-page.body") :on-accept delete-fn})) do-duplicate #(st/emit! (dw/duplicate-page id)) do-rename #(st/emit! (dw/start-rename-page-item id))] [:* (when deletable? [:& menu-entry {:title (tr "workspace.assets.delete") :on-click do-delete}]) [:& menu-entry {:title (tr "workspace.assets.rename") :on-click do-rename}] [:& menu-entry {:title (tr "workspace.assets.duplicate") :on-click do-duplicate}]])) (mf/defc viewport-context-menu [] (let [focus (mf/deref refs/workspace-focus-selected) do-paste #(st/emit! (dw/paste-from-clipboard)) do-hide-ui #(st/emit! (-> (dw/toggle-layout-flag :hide-ui) (vary-meta assoc ::ev/origin "workspace-context-menu"))) do-toggle-focus-mode #(st/emit! (dw/toggle-focus-mode))] [:* [:& menu-entry {:title (tr "workspace.shape.menu.paste") :shortcut (sc/get-tooltip :paste) :on-click do-paste}] [:& menu-entry {:title (tr "workspace.shape.menu.hide-ui") :shortcut (sc/get-tooltip :hide-ui) :on-click do-hide-ui}] (when (d/not-empty? focus) [:& menu-entry {:title (tr "workspace.focus.focus-off") :shortcut (sc/get-tooltip :toggle-focus-mode) :on-click do-toggle-focus-mode}])])) (mf/defc grid-track-context-menu [{:keys [mdata] :as props}] (let [{:keys [type index grid-id]} mdata do-delete-track (mf/use-callback (mf/deps grid-id type index) (fn [] (st/emit! (dwsl/remove-layout-track [grid-id] type index)))) do-add-track-before (mf/use-callback (mf/deps grid-id type index) (fn [] (st/emit! (dwsl/add-layout-track [grid-id] type ctl/default-track-value index)))) do-add-track-after (mf/use-callback (mf/deps grid-id type index) (fn [] (st/emit! (dwsl/add-layout-track [grid-id] type ctl/default-track-value (inc index))))) do-duplicate-track (mf/use-callback (mf/deps grid-id type index) (fn [] (st/emit! (dwsl/duplicate-layout-track [grid-id] type index)))) do-delete-track-shapes (mf/use-callback (mf/deps grid-id type index) (fn [] (st/emit! (dwsl/remove-layout-track [grid-id] type index {:with-shapes? true}))))] (if (= type :column) [:* [:& menu-entry {:title (tr "workspace.context-menu.grid-track.column.duplicate") :on-click do-duplicate-track}] [:& menu-entry {:title (tr "workspace.context-menu.grid-track.column.add-before") :on-click do-add-track-before}] [:& menu-entry {:title (tr "workspace.context-menu.grid-track.column.add-after") :on-click do-add-track-after}] [:& menu-entry {:title (tr "workspace.context-menu.grid-track.column.delete") :on-click do-delete-track}] [:& menu-entry {:title (tr "workspace.context-menu.grid-track.column.delete-shapes") :on-click do-delete-track-shapes}]] [:* [:& menu-entry {:title (tr "workspace.context-menu.grid-track.row.duplicate") :on-click do-duplicate-track}] [:& menu-entry {:title (tr "workspace.context-menu.grid-track.row.add-before") :on-click do-add-track-before}] [:& menu-entry {:title (tr "workspace.context-menu.grid-track.row.add-after") :on-click do-add-track-after}] [:& menu-entry {:title (tr "workspace.context-menu.grid-track.row.delete") :on-click do-delete-track}] [:& menu-entry {:title (tr "workspace.context-menu.grid-track.row.delete-shapes") :on-click do-delete-track-shapes}]]))) (mf/defc grid-cells-context-menu [{:keys [mdata] :as props}] (let [{:keys [grid cells]} mdata single? (= (count cells) 1) can-merge? (mf/use-memo (mf/deps cells) #(ctl/valid-area-cells? cells)) do-merge-cells (mf/use-callback (mf/deps grid cells) (fn [] (st/emit! (dwsl/merge-cells (:id grid) (map :id cells))))) do-create-board (mf/use-callback (mf/deps grid cells) (fn [] (st/emit! (dwsl/create-cell-board (:id grid) (map :id cells)))))] [:* (when (not single?) [:& menu-entry {:title (tr "workspace.context-menu.grid-cells.merge") :on-click do-merge-cells :disabled (not can-merge?)}]) (when single? [:& menu-entry {:title (tr "workspace.context-menu.grid-cells.area") :on-click do-merge-cells}]) [:& menu-entry {:title (tr "workspace.context-menu.grid-cells.create-board") :on-click do-create-board :disabled (and (not single?) (not can-merge?))}]])) (mf/defc context-menu [] (let [mdata (mf/deref menu-ref) top (- (get-in mdata [:position :y]) 20) left (get-in mdata [:position :x]) dropdown-ref (mf/use-ref)] (mf/use-effect (mf/deps mdata) #(let [dropdown (mf/ref-val dropdown-ref)] (when dropdown (let [bounding-rect (dom/get-bounding-rect dropdown) window-size (dom/get-window-size) delta-x (max (- (+ (:right bounding-rect) 250) (:width window-size)) 0) delta-y (max (- (:bottom bounding-rect) (:height window-size)) 0) new-style (str "top: " (- top delta-y) "px; " "left: " (- left delta-x) "px;")] (when (or (> delta-x 0) (> delta-y 0)) (.setAttribute ^js dropdown "style" new-style)))))) [:& dropdown {:show (boolean mdata) :on-close #(st/emit! dw/hide-context-menu)} [:div {:class (stl/css :workspace-context-menu) :ref dropdown-ref :style {:top top :left left} :on-context-menu prevent-default} [:ul {:class (stl/css :context-list)} (case (:kind mdata) :shape [:& shape-context-menu {:mdata mdata}] :page [:& page-item-context-menu {:mdata mdata}] :grid-track [:& grid-track-context-menu {:mdata mdata}] :grid-cells [:& grid-cells-context-menu {:mdata mdata}] [:& viewport-context-menu {:mdata mdata}])]]]))