Skip to content

Commit e32e56b

Browse files
Inspector Windows (#452)
`clerk.render/show-window` renders a floating, draggable, resizable and dockable window that can show arbitrary content that always lives on top of everything else. You can use it together with `clerk.render/inspect` in any cljs namespace to e.g. inspect data structures or quickly visualize a dataset.
1 parent f01d3b4 commit e32e56b

File tree

2 files changed

+181
-0
lines changed

2 files changed

+181
-0
lines changed

src/nextjournal/clerk/render.cljs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
[nextjournal.clerk.render.hooks :as hooks]
1717
[nextjournal.clerk.render.localstorage :as localstorage]
1818
[nextjournal.clerk.render.navbar :as navbar]
19+
[nextjournal.clerk.render.window :as window]
1920
[nextjournal.clerk.viewer :as viewer]
2021
[nextjournal.markdown.transform :as md.transform]
2122
[reagent.core :as r]
@@ -598,6 +599,9 @@
598599
(swap! !state update :desc viewer/merge-presentations more fetch-opts))))}
599600
[inspect-presented (:desc @!state)]]))
600601

602+
(defn show-window [& content]
603+
[window/show content])
604+
601605
(defn root []
602606
[:<>
603607
[inspect-presented @!doc]
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
(ns nextjournal.clerk.render.window
2+
(:require [applied-science.js-interop :as j]
3+
[nextjournal.clerk.render.hooks :as hooks]))
4+
5+
(defn resizer [{:keys [on-resize on-resize-start on-resize-end] :or {on-resize-start #() on-resize-end #()}}]
6+
(let [!direction (hooks/use-state nil)
7+
!mouse-down (hooks/use-state false)
8+
handle-mouse-down (fn [dir]
9+
(on-resize-start)
10+
(reset! !direction dir)
11+
(reset! !mouse-down true))]
12+
(hooks/use-effect (fn []
13+
(let [handle-mouse-move (fn [e]
14+
(when-let [dir @!direction]
15+
(on-resize dir (.-movementX e) (.-movementY e))))]
16+
(when @!mouse-down
17+
(js/addEventListener "mousemove" handle-mouse-move))
18+
#(js/removeEventListener "mousemove" handle-mouse-move)))
19+
[!mouse-down !direction on-resize])
20+
(hooks/use-effect (fn []
21+
(let [handle-mouse-up (fn []
22+
(on-resize-end)
23+
(reset! !mouse-down false))]
24+
(js/addEventListener "mouseup" handle-mouse-up)
25+
#(js/removeEventListener "mouseup" handle-mouse-up))))
26+
[:<>
27+
[:div.absolute.z-2.cursor-nwse-resize
28+
{:on-mouse-down #(handle-mouse-down :top-left)
29+
:class "w-[14px] h-[14px] -left-[7px] -top-[7px]"}]
30+
[:div.absolute.z-1.left-0.w-full.cursor-ns-resize
31+
{:on-mouse-down #(handle-mouse-down :top)
32+
:class "h-[4px] -top-[4px]"}]
33+
[:div.absolute.z-2.cursor-nesw-resize
34+
{:on-mouse-down #(handle-mouse-down :top-right)
35+
:class "w-[14px] h-[14px] -right-[7px] -top-[7px]"}]
36+
[:div.absolute.z-1.top-0.h-full.cursor-ew-resize
37+
{:on-mouse-down #(handle-mouse-down :right)
38+
:class "w-[4px] -right-[2px]"}]
39+
[:div.absolute.z-2.cursor-nwse-resize
40+
{:on-mouse-down #(handle-mouse-down :bottom-right)
41+
:class "w-[14px] h-[14px] -right-[7px] -bottom-[7px]"}]
42+
[:div.absolute.z-1.bottom-0.w-full.cursor-ns-resize
43+
{:on-mouse-down #(handle-mouse-down :bottom)
44+
:class "h-[4px] -left-[2px]"}]
45+
[:div.absolute.z-2.cursor-nesw-resize
46+
{:on-mouse-down #(handle-mouse-down :bottom-left)
47+
:class "w-[14px] h-[14px] -left-[7px] -bottom-[7px]"}]
48+
[:div.absolute.z-1.left-0.top-0.h-full.cursor-ew-resize
49+
{:on-mouse-down #(handle-mouse-down :left)
50+
:class "w-[4px]"}]]))
51+
52+
(defn header [{:keys [on-drag on-drag-start on-drag-end] :or {on-drag-start #() on-drag-end #()}}]
53+
(let [!mouse-down (hooks/use-state false)]
54+
(hooks/use-effect (fn []
55+
(let [handle-mouse-up (fn []
56+
(on-drag-end)
57+
(reset! !mouse-down false))]
58+
(js/addEventListener "mouseup" handle-mouse-up)
59+
#(js/addEventListener "mouseup" handle-mouse-up))))
60+
(hooks/use-effect (fn []
61+
(let [handle-mouse-move #(on-drag {:x (.-clientX %) :y (.-clientY %) :dx (.-movementX %) :dy (.-movementY %)})]
62+
(when @!mouse-down
63+
(js/addEventListener "mousemove" handle-mouse-move))
64+
#(js/removeEventListener "mousemove" handle-mouse-move)))
65+
[!mouse-down on-drag])
66+
[:div.bg-slate-100.hover:bg-slate-200.dark:bg-slate-800.dark:hover:bg-slate-700.cursor-move.w-full.rounded-t-lg
67+
{:class "h-[14px]"
68+
:on-mouse-down (fn [event]
69+
(on-drag-start)
70+
(reset! !mouse-down {:start-x (.-screenX event) :start-y (.-screenY event)}))}]))
71+
72+
(defn resize-top [panel {:keys [top height]} dy]
73+
(j/assoc-in! panel [:style :height] (str (- height dy) "px"))
74+
(j/assoc-in! panel [:style :top] (str (+ top dy) "px")))
75+
76+
(defn resize-right [panel {:keys [width]} dx]
77+
(j/assoc-in! panel [:style :width] (str (+ width dx) "px")))
78+
79+
(defn resize-bottom [panel {:keys [height]} dy]
80+
(j/assoc-in! panel [:style :height] (str (+ height dy) "px")))
81+
82+
(defn resize-left [panel {:keys [left width]} dx]
83+
(j/assoc-in! panel [:style :width] (str (- width dx) "px"))
84+
(j/assoc-in! panel [:style :left] (str (+ left dx) "px")))
85+
86+
(defn dock-at-top [panel]
87+
(-> panel
88+
(j/assoc-in! [:style :width] "calc(100vw - 10px)")
89+
(j/assoc-in! [:style :height] "33vh")
90+
(j/assoc-in! [:style :top] "5px")
91+
(j/assoc-in! [:style :left] "5px")))
92+
93+
(defn dock-at-right [panel]
94+
(-> panel
95+
(j/assoc-in! [:style :width] "33vw")
96+
(j/assoc-in! [:style :height] "calc(100vh - 10px)")
97+
(j/assoc-in! [:style :top] "5px")
98+
(j/assoc-in! [:style :left] "calc(100vw - 33vw - 5px)")))
99+
100+
(defn dock-at-bottom [panel]
101+
(-> panel
102+
(j/assoc-in! [:style :width] "calc(100vw - 10px)")
103+
(j/assoc-in! [:style :height] "33vh")
104+
(j/assoc-in! [:style :top] "calc(100vh - 33vh - 10px)")
105+
(j/assoc-in! [:style :left] "5px")))
106+
107+
(defn dock-at-left [panel]
108+
(-> panel
109+
(j/assoc-in! [:style :width] "33vw")
110+
(j/assoc-in! [:style :height] "calc(100vh - 10px)")
111+
(j/assoc-in! [:style :top] "5px")
112+
(j/assoc-in! [:style :left] "5px")))
113+
114+
(defn show [& content]
115+
(let [!panel-ref (hooks/use-ref nil)
116+
!dragging? (hooks/use-state nil)
117+
!dockable-at (hooks/use-state nil)
118+
!docking-ref (hooks/use-ref nil)]
119+
[:<>
120+
[:div.fixed.border-2.border-dashed.border-indigo-600.border-opacity-70.bg-indigo-600.bg-opacity-30.pointer-events-none.transition-all.rounded-lg
121+
{:class (str "z-[999] " (if-let [side @!dockable-at]
122+
(str "opacity-100 " (case side
123+
:top "left-[5px] top-[5px] right-[5px] h-[33vh]"
124+
:left "left-[5px] top-[5px] bottom-[5px] w-[33vw]"
125+
:bottom "left-[5px] bottom-[5px] right-[5px] h-[33vh]"
126+
:right "right-[5px] top-[5px] bottom-[5px] w-[33vw]"))
127+
"opacity-0 "))}]
128+
[:div.fixed.bg-white.dark:bg-slate-900.shadow-xl.text-slate-800.dark:text-slate-100.rounded-lg.flex.flex-col.hover:ring-2
129+
{:class (str "z-[1000] " (if @!dragging? "ring-indigo-600 select-none ring-2 " "ring-slate-300 dark:ring-slate-700 ring-1 "))
130+
:ref !panel-ref
131+
:style {:top 30 :right 30 :width 400 :height 400}}
132+
[resizer {:on-resize (fn [dir dx dy]
133+
(when-let [panel @!panel-ref]
134+
(let [rect (j/lookup (.getBoundingClientRect panel))]
135+
(case dir
136+
:top-left (do (resize-top panel rect dy)
137+
(resize-left panel rect dx))
138+
:top (resize-top panel rect dy)
139+
:top-right (do (resize-top panel rect dy)
140+
(resize-right panel rect dx))
141+
:right (resize-right panel rect dx)
142+
:bottom-right (do (resize-bottom panel rect dy)
143+
(resize-right panel rect dx))
144+
:bottom (resize-bottom panel rect dy)
145+
:bottom-left (do (resize-bottom panel rect dy)
146+
(resize-left panel rect dx))
147+
:left (resize-left panel rect dx)))))
148+
:on-resize-start #(reset! !dragging? true)
149+
:on-resize-end #(reset! !dragging? false)}]
150+
[header {:on-drag (fn [{:keys [x y dx dy]}]
151+
(when-let [panel @!panel-ref]
152+
(let [{:keys [left top width]} (j/lookup (.getBoundingClientRect panel))
153+
x-edge-offset 20
154+
y-edge-offset 10
155+
vw js/innerWidth
156+
vh js/innerHeight]
157+
(reset! !dockable-at (cond
158+
(zero? x) :left
159+
(>= x (- vw 2)) :right
160+
(<= y 0) :top
161+
(>= y (- vh 2)) :bottom
162+
:else nil))
163+
(reset! !docking-ref @!dockable-at)
164+
(j/assoc-in! panel [:style :left] (str (min (- vw x-edge-offset) (max (+ x-edge-offset (- width)) (+ left dx))) "px"))
165+
(j/assoc-in! panel [:style :top] (str (min (- vh y-edge-offset) (max y-edge-offset (+ top dy))) "px")))))
166+
:on-drag-start #(reset! !dragging? true)
167+
:on-drag-end (fn []
168+
(when-let [side @!docking-ref]
169+
(let [panel @!panel-ref]
170+
(case side
171+
:top (dock-at-top panel)
172+
:right (dock-at-right panel)
173+
:bottom (dock-at-bottom panel)
174+
:left (dock-at-left panel))))
175+
(reset! !dockable-at nil)
176+
(reset! !docking-ref nil))}]
177+
(into [:div.p-3.flex-auto.overflow-auto] content)]]))

0 commit comments

Comments
 (0)