Skip to content

Commit 5bebc0e

Browse files
committed
feat(core): plugin split pane functionality
1 parent 3aca2ec commit 5bebc0e

7 files changed

Lines changed: 146 additions & 36 deletions

File tree

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"size-limit": [
4949
{
5050
"path": "packages/devtools/dist/esm/index.js",
51-
"limit": "30 KB"
51+
"limit": "33 KB"
5252
},
5353
{
5454
"path": "packages/event-bus-client/dist/esm/plugin.js",

‎packages/devtools/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"build": "vite build"
4949
},
5050
"dependencies": {
51+
"@neodrag/solid": "3.0.0-next.8",
5152
"@solid-primitives/keyboard": "^1.2.8",
5253
"@tanstack/devtools-event-bus": "workspace:*",
5354
"@tanstack/devtools-ui": "workspace:*",

‎packages/devtools/src/context/devtools-store.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export type DevtoolsStore = {
6060
state: {
6161
activeTab: TabName
6262
height: number
63-
activePlugin?: string | undefined
63+
activePlugins: Array<string>
6464
persistOpen: boolean
6565
}
6666
plugins?: Array<TanStackDevtoolsPlugin>
@@ -79,7 +79,7 @@ export const initialState: DevtoolsStore = {
7979
state: {
8080
activeTab: 'plugins',
8181
height: 400,
82-
activePlugin: undefined,
82+
activePlugins: [],
8383
persistOpen: false,
8484
},
8585
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { createContext, createSignal, useContext } from 'solid-js'
2+
3+
import type { ParentComponent } from 'solid-js'
4+
5+
type DropZone = {
6+
id: string
7+
name: string
8+
ref: HTMLElement
9+
}
10+
11+
const useDropzone = () => {
12+
const [isDragging, setDragging] = createSignal(false)
13+
const dropZones: Array<DropZone> = []
14+
15+
const registerDropZone = (zone: DropZone) => {
16+
dropZones.push(zone)
17+
}
18+
19+
const checkDrop = (dragEl: HTMLElement): string | null => {
20+
const dragRect = dragEl.getBoundingClientRect()
21+
for (const { ref, name } of dropZones) {
22+
const dropRect = ref.getBoundingClientRect()
23+
const isInside =
24+
dragRect.left >= dropRect.left &&
25+
dragRect.right <= dropRect.right &&
26+
dragRect.top >= dropRect.top &&
27+
dragRect.bottom <= dropRect.bottom
28+
if (isInside) return name
29+
}
30+
return null
31+
}
32+
33+
return { isDragging, setDragging, checkDrop, registerDropZone }
34+
}
35+
36+
type ContextType = ReturnType<typeof useDropzone>
37+
38+
const DropzoneContext = createContext<ContextType | undefined>(undefined)
39+
40+
export const DropzoneProvider: ParentComponent = (props) => {
41+
const value = useDropzone()
42+
43+
return (
44+
<DropzoneContext.Provider value={value}>
45+
{props.children}
46+
</DropzoneContext.Provider>
47+
)
48+
}
49+
50+
export function useDropzoneContext() {
51+
const context = useContext(DropzoneContext)
52+
53+
if (context === undefined) {
54+
throw new Error(
55+
`useDropzoneContext must be used within a DropzoneClientProvider`,
56+
)
57+
}
58+
59+
return context
60+
}

‎packages/devtools/src/context/use-devtools-context.ts‎

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,35 @@ export const usePlugins = () => {
2323
const { setForceExpand } = useDrawContext()
2424

2525
const plugins = createMemo(() => store.plugins)
26-
const activePlugin = createMemo(() => store.state.activePlugin)
26+
const activePlugins = createMemo(() => store.state.activePlugins)
2727

2828
createEffect(() => {
29-
if (activePlugin() == null) {
29+
if (activePlugins().length > 0) {
3030
setForceExpand(false)
3131
} else {
3232
setForceExpand(true)
3333
}
3434
})
3535

36-
const setActivePlugin = (pluginId: string) => {
37-
setStore((prev) => ({
38-
...prev,
39-
state: {
40-
...prev.state,
41-
activePlugin: pluginId,
42-
},
43-
}))
36+
const toggleActivePlugins = (pluginId: string) => {
37+
setStore((prev) => {
38+
const isActive = prev.state.activePlugins.includes(pluginId)
39+
40+
const updatedPlugins = isActive
41+
? prev.state.activePlugins.filter((id) => id !== pluginId)
42+
: [...prev.state.activePlugins, pluginId]
43+
44+
return {
45+
...prev,
46+
state: {
47+
...prev.state,
48+
activePlugins: updatedPlugins,
49+
},
50+
}
51+
})
4452
}
4553

46-
return { plugins, setActivePlugin, activePlugin }
54+
return { plugins, toggleActivePlugins, activePlugins }
4755
}
4856

4957
export const useDevtoolsState = () => {
Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,33 @@
1-
import { For, createEffect } from 'solid-js'
1+
import { For, createEffect, createMemo, createSignal } from 'solid-js'
22
import clsx from 'clsx'
33
import { useDrawContext } from '../context/draw-context'
44
import { usePlugins } from '../context/use-devtools-context'
55
import { useStyles } from '../styles/use-styles'
66
import { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from '../constants'
77

88
export const PluginsTab = () => {
9-
const { plugins, activePlugin, setActivePlugin } = usePlugins()
9+
const { plugins, activePlugins, toggleActivePlugins } = usePlugins()
1010
const { expanded, hoverUtils, animationMs } = useDrawContext()
11-
let activePluginRef: HTMLDivElement | undefined
11+
12+
const [pluginRefs, setPluginRefs] = createSignal(
13+
new Map<string, HTMLDivElement>(),
14+
)
15+
16+
const styles = useStyles()
1217

1318
createEffect(() => {
14-
const currentActivePlugin = plugins()?.find(
15-
(plugin) => plugin.id === activePlugin(),
19+
const currentActivePlugins = plugins()?.filter((plugin) =>
20+
activePlugins().includes(plugin.id!),
1621
)
17-
if (activePluginRef && currentActivePlugin) {
18-
currentActivePlugin.render(activePluginRef)
19-
}
22+
23+
currentActivePlugins?.forEach((plugin) => {
24+
const ref = pluginRefs().get(plugin.id!)
25+
26+
if (ref) {
27+
plugin.render(ref)
28+
}
29+
})
2030
})
21-
const styles = useStyles()
2231

2332
return (
2433
<div class={styles().pluginsTabPanel}>
@@ -30,12 +39,8 @@ export const PluginsTab = () => {
3039
},
3140
styles().pluginsTabDrawTransition(animationMs),
3241
)}
33-
onMouseEnter={() => {
34-
hoverUtils.enter()
35-
}}
36-
onMouseLeave={() => {
37-
hoverUtils.leave()
38-
}}
42+
onMouseEnter={() => hoverUtils.enter()}
43+
onMouseLeave={() => hoverUtils.leave()}
3944
>
4045
<div
4146
class={clsx(
@@ -56,11 +61,18 @@ export const PluginsTab = () => {
5661
: plugin.name(pluginHeading)
5762
}
5863
})
64+
65+
const isActive = createMemo(() =>
66+
activePlugins().includes(plugin.id!),
67+
)
68+
5969
return (
6070
<div
61-
onClick={() => setActivePlugin(plugin.id!)}
71+
onClick={() => {
72+
toggleActivePlugins(plugin.id!)
73+
}}
6274
class={clsx(styles().pluginName, {
63-
active: activePlugin() === plugin.id,
75+
active: isActive(),
6476
})}
6577
>
6678
<h3 id={PLUGIN_TITLE_CONTAINER_ID} ref={pluginHeading} />
@@ -71,11 +83,21 @@ export const PluginsTab = () => {
7183
</div>
7284
</div>
7385

74-
<div
75-
id={PLUGIN_CONTAINER_ID}
76-
ref={activePluginRef}
77-
class={styles().pluginsTabContent}
78-
></div>
86+
<For each={activePlugins()}>
87+
{(pluginId) => (
88+
<div
89+
id={`${PLUGIN_CONTAINER_ID}-${pluginId}`}
90+
ref={(el) =>
91+
setPluginRefs((prev) => {
92+
const updated = new Map(prev)
93+
updated.set(pluginId, el)
94+
return updated
95+
})
96+
}
97+
class={styles().pluginsTabContent}
98+
/>
99+
)}
100+
</For>
79101
</div>
80102
)
81103
}

‎pnpm-lock.yaml‎

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)