Skip to content

Commit

Permalink
Rewrite popup menu
Browse files Browse the repository at this point in the history
Fixes #348
  • Loading branch information
kimo-k committed Nov 30, 2023
1 parent 9168813 commit e503088
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 188 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ All notable changes to this project will be documented in this file. This change
- Upgrade reagent to 1.2.0
- Namespace aliasing can be toggled in the settings.
- Namespace aliasing is more performant, especially when turned off.
- Right-click popup menu "Copy Object" replaced with "Copy Subtree."
- Copies parent subtree of the clicked item.

#### Fixed

- Right-click popup menu is positioned more accurately. See #348.

## 1.8.1 (2023-07-26)

Expand Down
168 changes: 31 additions & 137 deletions src/day8/re_frame_10x/components/cljs_devtools.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,9 @@
[day8.re-frame-10x.styles :as styles]
[day8.re-frame-10x.panels.app-db.events :as app-db.events]
[day8.re-frame-10x.panels.app-db.subs :as app-db.subs]
[day8.re-frame-10x.fx.clipboard :as clipboard]
[day8.re-frame-10x.inlined-deps.reagent.v1v2v0.reagent.core :as r]
[day8.re-frame-10x.inlined-deps.reagent.v1v2v0.reagent.dom :as dom]
[day8.re-frame-10x.tools.coll :as tools.coll]
[day8.re-frame-10x.tools.datafy :as tools.datafy]
[day8.re-frame-10x.tools.reader.edn :as reader.edn]
[day8.re-frame-10x.panels.settings.subs :as settings.subs])
(:import
[goog.dom TagName]))
[day8.re-frame-10x.panels.settings.subs :as settings.subs]))

(def default-config @devtools.prefs/default-config)

Expand Down Expand Up @@ -314,146 +308,46 @@
(prn-str-render data)
(jsonml->hiccup (header data nil) (conj path 0)))]))

(def event-log (atom '())) ;;stores a history of the events, treated as a stack

;; `html-element` is the html element that has received the right click
;; `app-db` is the full app db
;; `path` is the current path at the point where the popup is clicked in `data`
;; `html-target`, optional, is the element which the menus will be rendered in
(defn build-popup
[app-db path indexed-path html-element offset-x offset-y & [html-target]]
(let [popup-menu (goog.ui.PopupMenu.)
js-menu-style (-> #js {:text-align "center"
:padding "10px"
:border "1px solid #b9bdc6"}
(goog.style.toStyleAttribute))
create-menu-item (fn [menu-text]
(-> (goog.dom.createDom
TagName.DIV
#js {}
(goog.dom.createDom TagName.SPAN #js {} menu-text))
(doto (.setAttribute "style" js-menu-style))
goog.ui.MenuItem.))
copy-path-item (create-menu-item "Copy path")
copy-obj-item (create-menu-item "Copy object")
copy-repl-item (create-menu-item "Copy REPL command")
element-rect (.getBoundingClientRect html-element)
target-rect (when html-target (.getBoundingClientRect html-target))
target-x-offset (when target-rect (+ (.-left target-rect) (.-scrollX js/window)))
element-x-pos (+ (.-left element-rect) (.-scrollX js/window))
;; element-x-pos is relative to window, so we remove offset of element we're rendering in below
menu-x-pos (+ offset-x
(if target-x-offset
(- element-x-pos target-x-offset)
element-x-pos))
menu-y-pos (+ offset-y (.-top element-rect) (.-scrollY js/window))]
(doto copy-path-item
(.addClassName "copy-path")
(.addClassName "10x-menu-item"))
(doto copy-obj-item
(.addClassName "copy-object")
(.addClassName "10x-menu-item"))
(doto copy-repl-item
(.addClassName "copy-repl")
(.addClassName "10x-menu-item"))
(doto popup-menu
(.addItem copy-path-item)
(.addItem copy-obj-item)
(.addItem copy-repl-item)
(.showAt menu-x-pos menu-y-pos)
(.render (or html-target html-element))) ;;if menu target is not supplied we render on clicked element
(goog.object.forEach
goog.ui.Component.EventType
(fn [type]
(goog.events.listen
popup-menu
type
(fn [e]
(cond
(= (.-type e) "hide")
(when (= (peek @event-log) "highlight")
;; if the last event registered is 'highlight' then we should not close the dialog
;; `highlight` event is dispatched right before `action`. Action would not be dispatched
;; if the preceding `highlight` closes the dialog
(.preventDefault e))

;; `action` is thrown after hide
;; `action` is thrown before unhighlight -> hide -> leave
(= (.-type e) "action")
(let [class-names (-> e .-target .getExtraClassNames js->clj)
object (tools.coll/get-in-with-lists-and-sets app-db path)]
(swap! event-log conj "action")
(cond
(some (fn [class-name] (= class-name "copy-object")) class-names)
(if (or object (= object false))
(clipboard/copy! object) ;; note we can't copy nil objects
(js/console.error "Could not copy!"))

(some (fn [class-name] (= class-name "copy-path")) class-names)
(clipboard/copy! path)

(some (fn [class-name] (= class-name "copy-repl")) class-names)
(clipboard/copy! (str "(simple-render-with-path-annotations " app-db " " ["app-db-path" indexed-path] {} ")"))))

:else
(swap! event-log conj (.-type e)))))))))

(defn simple-render-with-path-annotations
[data indexed-path {:keys [object path-id sort?] :as opts} & [class]]
(let [render-paths? (rf/subscribe [::app-db.subs/data-path-annotations?])
[{:keys [data path path-id sort?] :as opts}]
(let [render-paths? @(rf/subscribe [::app-db.subs/data-path-annotations?])
open-new-inspectors? @(rf/subscribe [::settings.subs/open-new-inspectors?])
ns->alias @(rf/subscribe [::settings.subs/ns->alias])
alias? (and (seq ns->alias)
@(rf/subscribe [::settings.subs/alias-namespaces?]))
data (cond-> data
alias? (tools.datafy/alias-namespaces ns->alias)
sort? tools.datafy/deep-sorted-map)
input-field-path (second indexed-path) ;;path typed in input-box
shadow-root (-> (.getElementById js/document "--re-frame-10x--") ;;main shadow-root html component
.-shadowRoot
.-children)
root-div (-> (filter (fn [element] ;; root re-frame-10x parent div
(= (.-tagName element) "DIV")) shadow-root)
first)
menu-html-target (some-> root-div .-firstChild)
menu-html-target (when (and menu-html-target
(= (.-childElementCount menu-html-target) 2)) ;; we will render menus on this element
(.-lastChild menu-html-target))
;; triggered during `contextmenu` event when a path annotation is right-clicked
menu-listener (fn [event]
;; at this stage `data` might have changed
;; we have to rely on `current-data` alias `obj`
(when-let [target (some-> event .-target .-parentElement)]
(let [path (.getAttribute target "data-path")
path-obj (reader.edn/read-string-maybe path)
offset-x (.-offsetX event)
offset-y (.-offsetY event)]
(.preventDefault event)
(when menu-html-target (build-popup object path-obj indexed-path target offset-x offset-y menu-html-target)))))
;; triggered during `click` event when a path annotation is clicked
click-listener (fn [event]
(when-let [path (some-> event .-target .-parentElement (.getAttribute "data-path"))]
(when (= (.-button event) 0) ;;left click btn
(rf/dispatch [::app-db.events/update-path {:id path-id :path path}]))))
;; triggered during `mousedown` event when an element is clicked.
middle-click-listener (fn [event]
(when-let [target (some-> event .-target .-parentElement)]
(let [path (.getAttribute target "data-path")
btn (.-button event)]
(.preventDefault event)
(when (= btn 1) ;;middle click btn
(rf/dispatch [::app-db.events/create-path-and-skip-to path open-new-inspectors?])))))]
sort? tools.datafy/deep-sorted-map)]
[rc/box
:size "1"
:class (str (jsonml-style) " " class)
:class (jsonml-style)
:child
(if (prn-str-render? data)
(prn-str-render data)
(jsonml->hiccup-with-path-annotations
(header data nil {:render-paths? @render-paths?})
(conj indexed-path 0)
(or input-field-path [])
(assoc opts
:click-listener click-listener
:middle-click-listener middle-click-listener
:menu-listener menu-listener)))]))
(header data nil {:render-paths? render-paths?})
["app-db-path" path]
(or path [])
(merge
opts
{:click-listener #(when-let [path (some-> % .-target .-parentElement (.getAttribute "data-path"))]
(when (= (.-button %) 0)
(rf/dispatch [::app-db.events/update-path {:id path-id :path path}])))
:middle-click-listener #(when-let [target (some-> % .-target .-parentElement)]
(let [path (.getAttribute target "data-path")
btn (.-button %)]
(.preventDefault %)
(when (= btn 1)
(rf/dispatch
[::app-db.events/create-path-and-skip-to path open-new-inspectors?]))))
:menu-listener #(do (.preventDefault %)
(rf/dispatch
[::app-db.events/open-popup-menu
{:data data
:mouse-position [(.-clientX %) (.-clientY %)]
:path path
:data-path (some-> %
.-target
.-parentElement
(.getAttribute "data-path")
reader.edn/read-string-maybe)}]))})))]))
10 changes: 9 additions & 1 deletion src/day8/re_frame_10x/fx/window.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
[goog.object :as gobj]
[goog.string :as gstring]
[clojure.string :as string]
[day8.re-frame-10x.inlined-deps.reagent.v1v2v0.reagent.core :as r]
[day8.re-frame-10x.inlined-deps.re-frame.v1v3v0.re-frame.core :as rf]))

(def popout-window (r/atom nil))

(defn m->str
[m]
(->> m
Expand Down Expand Up @@ -50,9 +53,14 @@
(.write d window-html)
(gobj/set w "onload" (partial on-load w d))
(.close d)
(rf/dispatch on-success))
(rf/dispatch on-success)
(reset! popout-window w))
(rf/dispatch on-failure))))

(rf/reg-fx
::open-debugger-window
open-debugger-window)

(rf/reg-fx
::close-debugger-window
(fn [] (reset! popout-window nil)))
41 changes: 40 additions & 1 deletion src/day8/re_frame_10x/material.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,48 @@
[day8.re-frame-10x.styles :as styles])
(:refer-clojure :exclude [print]))

;; Icons from https://material.io/resources/icons/ 'Sharp' theme.
;; Most icons from https://material.io/resources/icons/ 'Sharp' theme.
;; Names have been kept the same for ease of reference.

(defn clojure [{:keys [size] ;; from https://brandeps.com
:or {size styles/gs-19s}}]
[:svg {:version "1.1"
:id "Layer_1"
:width size
:height size
:viewBox "0 0 512 512"
:style {:enable-background "new 0 0 512 512"}}
[:style {:type "text/css"} ".st0{fill:#444444;}"]
[:g
[:path {:class "st0"
:d "M256,0C114.8,0,0,114.8,0,256s114.8,256,256,256c141.2,0,256-114.8,256-256S397.1,0,256,0z M256,24.6 c127.8,0,231.3,103.6,231.3,231.4c0,10-0.7,19.8-1.9,29.5c-6.3,25-17.6,41.7-31.2,53.1c-20.8,17.5-48.4,23.1-74.1,23.1 c-7.2,0-14.3-0.4-20.9-1.2c27-26.6,43.7-63.6,43.7-104.6c0-81.2-65.8-147-147-147c-21,0-41.1,4.5-59.2,12.4 c-2.3-1.6-4.6-3.1-7.1-4.5c-10.1-5.7-31.3-14-58.2-14.1c-19.5-0.2-41.8,4.8-63.6,18.5C110,62.8,178.5,24.7,256,24.6L256,24.6z M215.6,339.8c3.8-16.4,14-42,23.3-63.1c2.6-6,5.2-11.6,7.5-16.5c14.5,51.7,23.7,82.4,40.1,103.3c2.5,3.1,5.2,5.9,8,8.5 c-12.1,4-25.1,6.3-38.6,6.3c-14.8,0-29-2.7-42.1-7.5c-0.3-3.4-0.4-6.7-0.4-9.9C213.4,352.9,214.2,345.6,215.6,339.8L215.6,339.8z M184.3,355c-30.6-22.2-50.6-58.2-50.6-99c0.1-41.4,20.6-77.9,52-100c6.9,4,13.1,8.6,18.2,14c10.1,10.3,21.3,33.1,29.2,52.7 c2.1,5.3,4,10.3,5.7,14.9C210.6,295.1,191.8,323.7,184.3,355z M275.9,281.5c-6.3-16.3-10-28.5-10-28.5l0,0 c-11.5-44.3-23.5-84.7-48.3-113c12.1-4,25-6.2,38.4-6.2c67.6,0.1,122.2,54.8,122.3,122.3c-0.1,40.5-19.8,76.4-50.1,98.6 c-4.5-1.3-7.7-2.4-9.2-3.1c-4.8-2.1-11.8-9.1-18.2-18.9C291.1,317.9,282.2,297.8,275.9,281.5L275.9,281.5z M256,487.3 C128.2,487.3,24.6,383.8,24.6,256c0-8.4,0.5-16.7,1.3-24.9c19-64.5,64.5-88.3,107.6-88.7c8.8,0,17.3,1.1,25.5,3.1 C128.4,172.4,109,211.9,109,256c0,81.2,65.8,147,147,147c23.5,0,45.6-5.5,65.3-15.3c11.7,4.1,25.1,6.6,41.3,8.6 c6.1,0.7,12.6,1.1,19.6,1.1c18.7-0.1,40.4-2.8,63.1-8.4C403.3,448.4,334.2,487.3,256,487.3L256,487.3z"}]]])

(defn data-object [{:keys [size]
:or {size styles/gs-19s}}]
[:svg {:enable-background "new 0 0 24 24"
:height size
:viewBox "0 0 24 24"
:width size}
[:g
[:rect {:fill "none" :height "24" :width "24"}]]
[:g
[:g
[:path {:d "M4,7v2c0,0.55-0.45,1-1,1H2v4h1c0.55,0,1,0.45,1,1v2c0,1.65,1.35,3,3,3h3v-2H7c-0.55,0-1-0.45-1-1v-2 c0-1.3-0.84-2.42-2-2.83v-0.34C5.16,11.42,6,10.3,6,9V7c0-0.55,0.45-1,1-1h3V4H7C5.35,4,4,5.35,4,7z"}]
[:path {:d "M21,10c-0.55,0-1-0.45-1-1V7c0-1.65-1.35-3-3-3h-3v2h3c0.55,0,1,0.45,1,1v2c0,1.3,0.84,2.42,2,2.83v0.34 c-1.16,0.41-2,1.52-2,2.83v2c0,0.55-0.45,1-1,1h-3v2h3c1.65,0,3-1.35,3-3v-2c0-0.55,0.45-1,1-1h1v-4H21z"}]]]])

(defn data-array [{:keys [size]
:or {size styles/gs-19s}}]
[:svg {:enable-background "new 0 0 24 24"
:height size
:viewBox "0 0 24 24"
:width size}
[:g
[:rect {:fill "none" :height "24" :width "24"}]]
[:g
[:g
[:polygon {:points "15,4 15,6 18,6 18,18 15,18 15,20 20,20 20,4"}]
[:polygon {:points "4,20 9,20 9,18 6,18 6,6 9,6 9,4 4,4"}]]]])

(defn add
[{:keys [size]}]
[:svg {:height size
Expand Down
3 changes: 2 additions & 1 deletion src/day8/re_frame_10x/navigation/events.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
[(rf/path [:settings :external-window?]) (local-storage/save "external-window?")]
(fn [_ _]
{:db false
:fx [[:dispatch-later {:ms 400 :dispatch [::settings.events/show-panel? true]}]]}))
:fx [[:dispatch-later {:ms 400 :dispatch [::settings.events/show-panel? true]}]
[::window/close-debugger-window]]}))

(rf/reg-event-db
::dismiss-popup-failed
Expand Down
Loading

0 comments on commit e503088

Please sign in to comment.