Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 178 additions & 57 deletions packages/router-devtools-core/src/BaseTanStackRouterDevtoolsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { clsx as cx } from 'clsx'
import { default as invariant } from 'tiny-invariant'
import { interpolatePath, rootRouteId, trimPath } from '@tanstack/router-core'
import { Show, createMemo, createSignal, onCleanup } from 'solid-js'
import {
For,
Match,
Show,
Switch,
createEffect,
createMemo,
createSignal,
onCleanup,
untrack,
} from 'solid-js'
import { useDevtoolsOnClose } from './context'
import { useStyles } from './useStyles'
import useLocalStorage from './useLocalStorage'
Expand All @@ -14,6 +24,7 @@ import { NavigateButton } from './NavigateButton'
import type {
AnyContext,
AnyRoute,
AnyRouteMatch,
AnyRouter,
FileRouteTypes,
MakeRouteMatchUnion,
Expand Down Expand Up @@ -54,6 +65,8 @@ export interface BaseDevtoolsPanelOptions {
shadowDOMTarget?: ShadowRoot
}

const HISTORY_LIMIT = 15

function Logo(props: any) {
const { className, ...rest } = props
const styles = useStyles()
Expand Down Expand Up @@ -257,16 +270,60 @@ export const BaseTanStackRouterDevtoolsPanel =

// useStore(router.__store)

const [showMatches, setShowMatches] = useLocalStorage(
'tanstackRouterDevtoolsShowMatches',
true,
)
const [currentTab, setCurrentTab] = useLocalStorage<
'routes' | 'matches' | 'history'
>('tanstackRouterDevtoolsActiveTab', 'routes')

const [activeId, setActiveId] = useLocalStorage(
'tanstackRouterDevtoolsActiveRouteId',
'',
)

const [history, setHistory] = createSignal<Array<AnyRouteMatch>>([])

createEffect(() => {
const matches = routerState().matches
const currentMatch = matches[matches.length - 1]
if (!currentMatch) {
return
}
// Read history WITHOUT tracking it to avoid infinite loops
const lastMatch = untrack(() => history()[0])
const sameLocation =
lastMatch &&
lastMatch.pathname === currentMatch.pathname &&
JSON.stringify(lastMatch.search) ===
JSON.stringify(currentMatch.search || {})
if (!lastMatch || !sameLocation) {
setHistory((prev) => {
const newHistory = [currentMatch, ...prev]
// truncate to ensure we don't overflow too much the ui
newHistory.splice(HISTORY_LIMIT)
return newHistory
})
}
})

createEffect(() => {
const currentPathname =
routerState().matches[routerState().matches.length - 1]

// Read history WITHOUT tracking it to avoid infinite loops
const lastPathname = untrack(() => {
const h = history()
return h[0]
})

if (!lastPathname || lastPathname.id !== currentPathname.id) {
setHistory((prev) => {
const newHistory = [currentPathname, ...prev]
// truncate to ensure we don't overflow too much the ui
newHistory.splice(15)
return newHistory
})
}
})

const activeMatch = createMemo(() => {
const matches = [
...(routerState().pendingMatches ?? []),
Expand Down Expand Up @@ -421,82 +478,146 @@ export const BaseTanStackRouterDevtoolsPanel =
<button
type="button"
onClick={() => {
setShowMatches(false)
setCurrentTab('routes')
}}
disabled={!showMatches()}
disabled={currentTab() === 'routes'}
class={cx(
styles().routeMatchesToggleBtn(!showMatches(), true),
styles().routeMatchesToggleBtn(
currentTab() === 'routes',
true,
),
)}
>
Routes
</button>
<button
type="button"
onClick={() => {
setShowMatches(true)
setCurrentTab('matches')
}}
disabled={showMatches()}
disabled={currentTab() === 'matches'}
class={cx(
styles().routeMatchesToggleBtn(!!showMatches(), false),
styles().routeMatchesToggleBtn(
currentTab() === 'matches',
true,
),
)}
>
Matches
</button>
<button
type="button"
onClick={() => {
setCurrentTab('history')
}}
disabled={currentTab() === 'history'}
class={cx(
styles().routeMatchesToggleBtn(
currentTab() === 'history',
false,
),
)}
>
History
</button>
</div>
<div class={styles().detailsHeaderInfo}>
<div>age / staleTime / gcTime</div>
</div>
</div>
<div class={cx(styles().routesContainer)}>
{!showMatches() ? (
<RouteComp
routerState={routerState}
router={router}
route={router().routeTree}
isRoot
activeId={activeId}
setActiveId={setActiveId}
/>
) : (
<div>
{(routerState().pendingMatches?.length
? routerState().pendingMatches
: routerState().matches
)?.map((match: any, _i: any) => {
return (
<div
role="button"
aria-label={`Open match details for ${match.id}`}
onClick={() =>
setActiveId(activeId() === match.id ? '' : match.id)
}
class={cx(styles().matchRow(match === activeMatch()))}
>
<Switch>
<Match when={currentTab() === 'routes'}>
<RouteComp
routerState={routerState}
router={router}
route={router().routeTree}
isRoot
activeId={activeId}
setActiveId={setActiveId}
/>
</Match>
<Match when={currentTab() === 'matches'}>
<div>
{(routerState().pendingMatches?.length
? routerState().pendingMatches
: routerState().matches
)?.map((match: any, _i: any) => {
return (
<div
class={cx(
styles().matchIndicator(getStatusColor(match)),
)}
/>
<NavigateLink
left={
<NavigateButton
to={match.pathname}
params={match.params}
search={match.search}
router={router}
/>
role="button"
aria-label={`Open match details for ${match.id}`}
onClick={() =>
setActiveId(activeId() === match.id ? '' : match.id)
}
right={<AgeTicker match={match} router={router} />}
class={cx(styles().matchRow(match === activeMatch()))}
>
<code class={styles().matchID}>
{`${match.routeId === rootRouteId ? rootRouteId : match.pathname}`}
</code>
</NavigateLink>
</div>
)
})}
</div>
)}
<div
class={cx(
styles().matchIndicator(getStatusColor(match)),
)}
/>
<NavigateLink
left={
<NavigateButton
to={match.pathname}
params={match.params}
search={match.search}
router={router}
/>
}
right={<AgeTicker match={match} router={router} />}
>
<code class={styles().matchID}>
{`${match.routeId === rootRouteId ? rootRouteId : match.pathname}`}
</code>
</NavigateLink>
</div>
)
})}
</div>
</Match>
<Match when={currentTab() === 'history'}>
<div>
<ul>
<For each={history()}>
{(match, index) => (
<li
class={cx(
styles().matchRow(match === activeMatch()),
)}
>
<div
class={cx(
styles().matchIndicator(
index() === 0 ? 'green' : 'gray',
),
)}
/>
<NavigateLink
left={
<NavigateButton
to={match.pathname}
params={match.params}
search={match.search}
router={router}
/>
}
right={
<AgeTicker match={match} router={router} />
}
>
<code class={styles().matchID}>
{`${match.routeId === rootRouteId ? rootRouteId : match.pathname}`}
</code>
</NavigateLink>
</li>
)}
</For>
</ul>
</div>
</Match>
</Switch>
</div>
</div>
{routerState().cachedMatches.length ? (
Expand Down
6 changes: 6 additions & 0 deletions packages/router-devtools-core/src/useStyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,12 @@ const stylesFactory = (shadowDOMTarget?: ShadowRoot) => {
overflow-y: auto;
max-height: 50%;
`,
historyContainer: css`
display: flex;
flex: 1 1 auto;
overflow-y: auto;
max-height: 50%;
`,
maskedBadgeContainer: css`
flex: 1;
justify-content: flex-end;
Expand Down
Loading