-
-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathShell.tsx
More file actions
169 lines (152 loc) · 5.04 KB
/
Shell.tsx
File metadata and controls
169 lines (152 loc) · 5.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import {
createEffect,
createMemo,
createSignal,
onCleanup,
onMount,
} from 'solid-js'
import { Header, HeaderLogo, MainPanel } from '@tanstack/devtools-ui'
import { useStyles } from '../styles/use-styles'
import { usePacerDevtoolsState } from '../PacerContextProvider'
import { UTIL_GROUPS } from './util-groups'
import { UtilList } from './UtilList'
import { DetailsPanel } from './DetailsPanel'
import type { StateKey } from './util-groups'
function readResizeObserverBlockSize(entry: ResizeObserverEntry): number {
const [box] = entry.borderBoxSize
if (box !== undefined) return box.blockSize
return entry.contentRect.height
}
/** Outer plugin slot from TanStack Devtools (not the inner React mount div, which can report a tiny height). */
function resolvePluginHost(el: HTMLElement): HTMLElement | null {
const byId = el.closest<HTMLElement>('[id^="plugin-container-"]')
if (byId) return byId
return (
el.parentElement?.parentElement?.parentElement ??
el.parentElement?.parentElement ??
el.parentElement
)
}
export function Shell() {
const styles = useStyles()
const state = usePacerDevtoolsState()
const utilState = () => state
const [selectedKey, setSelectedKey] = createSignal<string | null>(null)
const [leftPanelWidth, setLeftPanelWidth] = createSignal(300)
const [isDragging, setIsDragging] = createSignal(false)
const [shellRootEl, setShellRootEl] = createSignal<HTMLDivElement | null>(
null,
)
const [slotHeightPx, setSlotHeightPx] = createSignal<number | undefined>(
undefined,
)
const selectedInstance = createMemo(() => {
const key = selectedKey()
if (!key) return null
for (const group of UTIL_GROUPS) {
const instance = (utilState() as unknown as Record<StateKey, Array<any>>)[
group.key
].find((inst) => inst.key === key)
if (instance) return { instance, type: group.displayName }
}
return null
})
let dragStartX = 0
let dragStartWidth = 0
const handleMouseDown = (e: MouseEvent) => {
e.preventDefault()
e.stopPropagation()
setIsDragging(true)
document.body.style.cursor = 'col-resize'
document.body.style.userSelect = 'none'
dragStartX = e.clientX
dragStartWidth = leftPanelWidth()
}
const handleMouseMove = (e: MouseEvent) => {
if (!isDragging()) return
e.preventDefault()
const deltaX = e.clientX - dragStartX
const newWidth = Math.max(150, Math.min(800, dragStartWidth + deltaX))
setLeftPanelWidth(newWidth)
}
const handleMouseUp = () => {
setIsDragging(false)
document.body.style.cursor = ''
document.body.style.userSelect = ''
}
onMount(() => {
document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseup', handleMouseUp)
})
onCleanup(() => {
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
})
/**
* TanStack Devtools plugin slots use height: 100% without min-height: 0 on flex
* ancestors, so percentage height often never constrains our tree. Observing the
* outer `plugin-container-*` host (not the immediate React mount wrapper) and
* setting an explicit pixel height makes inner overflow-y regions scroll without
* capping to the mount div’s collapsed height.
*/
createEffect(() => {
const el = shellRootEl()
if (!el || typeof ResizeObserver === 'undefined') return
const pluginSlot = resolvePluginHost(el)
if (!pluginSlot) return
const ro = new ResizeObserver((entries) => {
const entry = entries[0]
if (!entry) return
const target = entry.target as HTMLElement
const h =
target.clientHeight > 0
? target.clientHeight
: readResizeObserverBlockSize(entry)
if (h > 0) setSlotHeightPx(Math.round(h))
})
ro.observe(pluginSlot)
onCleanup(() => ro.disconnect())
})
return (
<div
ref={setShellRootEl}
class={styles().shellRoot}
style={
slotHeightPx() !== undefined
? `--shell-slot-height: ${slotHeightPx()}px`
: undefined
}
>
<MainPanel class={styles().mainPanelShell}>
<Header>
<HeaderLogo flavor={{ light: '#84cc16', dark: '#84cc16' }}>
TanStack Pacer
</HeaderLogo>
</Header>
<div class={styles().mainContainer}>
<div
class={styles().leftPanel}
style={`--left-panel-width: ${leftPanelWidth()}px`}
>
<UtilList
selectedKey={selectedKey}
setSelectedKey={setSelectedKey}
utilState={utilState}
/>
</div>
<div
class={`${styles().dragHandle} ${isDragging() ? 'dragging' : ''}`}
onMouseDown={handleMouseDown}
/>
<div class={styles().rightPanel}>
<div class={styles().panelHeader}>Details</div>
<DetailsPanel
selectedInstance={selectedInstance}
utilState={utilState}
/>
</div>
</div>
</MainPanel>
</div>
)
}