Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,12 @@ import type { HTMLProps } from 'react'
import { css } from '../../utils/css'
import type { DevToolsInfoPropsCore } from '../errors/dev-tools-indicator/dev-tools-info/dev-tools-info'
import { DevToolsInfo } from '../errors/dev-tools-indicator/dev-tools-info/dev-tools-info'
import { cx } from '../../utils/cx'
import {
type SegmentNode,
useSegmentTreeClientState,
} from '../../../../shared/lib/devtool/app-segment-tree'
import type { Trie, TrieNode } from '../../../../shared/lib/devtool/trie'

const IconLayout = (props: React.SVGProps<SVGSVGElement>) => {
return (
<svg
{...props}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16 12.5L15.9873 12.7559C15.8677 13.9323 14.9323 14.8677 13.7559 14.9873L13.5 15H2.5L2.24414 14.9873C1.06772 14.8677 0.132274 13.9323 0.0126953 12.7559L0 12.5V1H16V12.5ZM1.5 6.25488V12.5C1.5 13.0523 1.94772 13.5 2.5 13.5H4.99512V6.25488H1.5ZM6.24512 6.25488V13.5H13.5C14.0523 13.5 14.5 13.0523 14.5 12.5V6.25488H6.24512ZM1.5 5.00488H14.5V2.5H1.5V5.00488Z"
fill="currentColor"
/>
</svg>
)
}

const IconPage = (props: React.SVGProps<SVGSVGElement>) => {
return (
<svg
{...props}
viewBox="0 0 16 16"
fill="none"
strokeLinejoin="round"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M14.5 6.5V13.5C14.5 14.8807 13.3807 16 12 16H4C2.61929 16 1.5 14.8807 1.5 13.5V1.5V0H3H8H9.08579C9.351 0 9.60536 0.105357 9.79289 0.292893L14.2071 4.70711C14.3946 4.89464 14.5 5.149 14.5 5.41421V6.5ZM13 6.5V13.5C13 14.0523 12.5523 14.5 12 14.5H4C3.44772 14.5 3 14.0523 3 13.5V1.5H8V5V6.5H9.5H13ZM9.5 2.12132V5H12.3787L9.5 2.12132Z"
fill="currentColor"
/>
</svg>
)
}

const ICONS = {
layout: <IconLayout width={16} />,
page: <IconPage width={16} />,
}

function PageSegmentTree({ tree }: { tree: Trie<SegmentNode> | undefined }) {
if (!tree) {
return null
Expand All @@ -63,6 +22,7 @@ function PageSegmentTree({ tree }: { tree: Trie<SegmentNode> | undefined }) {
node={tree.getRoot()}
level={0}
segment=""
parentSegment=""
/>
</div>
)
Expand All @@ -71,19 +31,22 @@ function PageSegmentTree({ tree }: { tree: Trie<SegmentNode> | undefined }) {
function PageSegmentTreeLayerPresentation({
tree,
segment,
parentSegment,
node,
level,
}: {
tree: Trie<SegmentNode>
segment: string
parentSegment: string
node: TrieNode<SegmentNode>
level: number
}) {
const pagePath = node.value?.pagePath || ''
const nodeName = node.value?.type
const isFile = !!nodeName

const segments = pagePath.split('/') || []
const fileName = segments.pop() || ''
const fileName = isFile ? segments.pop() || '' : ''
const childrenKeys = Object.keys(node.children)

const sortedChildrenKeys = childrenKeys.sort((a, b) => {
Expand All @@ -97,65 +60,64 @@ function PageSegmentTreeLayerPresentation({
return a.localeCompare(b)
})

// check if it has file children
const hasFileChildren = sortedChildrenKeys.some((key) => {
const childNode = node.children[key]
return !!childNode?.value?.type
})

// If it's the 1st level and contains a file, use 'app' as the folder name
const folderName = level === 1 && isFile ? 'app' : parentSegment

return (
<div
className="segment-explorer-item"
data-nextjs-devtool-segment-explorer-segment={segment}
>
{fileName ? (
<div className={cx('segment-explorer-item-row')}>
<div className="segment-explorer-line">
<div className={`segment-explorer-line-text-${nodeName}`}>
<span
className={cx(
'segment-explorer-line-icon',
`segment-explorer-line-icon-${nodeName}`
)}
>
{nodeName === 'layout' ? ICONS.layout : ICONS.page}
</span>
<span className="segment-explorer-filename-path">{fileName}</span>
</div>
</div>
</div>
) : segment ? (
<div className={'segment-explorer-item-row'}>
<div className="segment-explorer-line">
<div className={`segment-explorer-line-text-${nodeName}`}>
<span
className={cx(
'segment-explorer-line-icon',
`segment-explorer-line-icon-${nodeName}`
)}
></span>
<span className="segment-explorer-filename-path">
{`${segment}/`}
</span>
<>
{isFile ? (
<div
className="segment-explorer-item"
data-nextjs-devtool-segment-explorer-segment={segment}
>
<div
className="segment-explorer-item-row"
style={{
// If it's children levels, show indents if there's any file at that level.
// Otherwise it's empty folder, no need to show indents.
...(level > 0 && isFile && { paddingLeft: `${level * 8}px` }),
}}
>
<div className="segment-explorer-line">
<div className={`segment-explorer-line-text-${nodeName}`}>
<div className="segment-explorer-filename">
{folderName && (
<span className="segment-explorer-filename--path">
{folderName}
</span>
)}
<span className="segment-explorer-filename--name">
{fileName}
</span>
</div>
</div>
</div>
</div>
</div>
) : null}

<div
className="segment-explorer-segment-children"
data-nextjs-devtool-segment-explorer-level={level}
>
{sortedChildrenKeys.map((childSegment) => {
const child = node.children[childSegment]
return (
child && (
<PageSegmentTreeLayerPresentation
key={childSegment}
segment={childSegment}
tree={tree}
node={child}
level={level + 1}
/>
)
)
})}
</div>
</div>
{sortedChildrenKeys.map((childSegment) => {
const child = node.children[childSegment]
if (!child) {
return null
}
return (
<PageSegmentTreeLayerPresentation
key={childSegment}
segment={childSegment}
parentSegment={segment}
tree={tree}
node={child}
level={hasFileChildren ? level + 1 : level}
/>
)
})}
</>
)
}

Expand All @@ -177,47 +139,46 @@ export function SegmentsExplorer(
export const DEV_TOOLS_INFO_RENDER_FILES_STYLES = css`
.segment-explorer-content {
overflow-y: auto;
padding: 0 12px;
font-size: var(--size-14);
margin: -12px -8px;
}

.segment-explorer-item {
margin: 4px 0;
}

.segment-explorer-item:nth-child(odd) {
background-color: var(--color-gray-100);
}

.segment-explorer-item-row {
display: flex;
align-items: center;
gap: 8px;
padding: 2px 0;
padding: 4px 24px;
border-radius: 6px;
}

[data-nextjs-devtool-segment-explorer-level].segment-explorer-segment-children {
padding-left: 20px;
}
[data-nextjs-devtool-segment-explorer-level='0'].segment-explorer-segment-children {
padding-left: 0px;
.segment-explorer-children--intended {
padding-left: 16px;
}

.segment-explorer-filename-path {
display: inline-block;
.segment-explorer-filename--path {
margin-right: 8px;
}

.segment-explorer-filename-path a {
color: inherit;
text-decoration: inherit;
.segment-explorer-filename--name {
color: var(--color-gray-800);
}

.segment-explorer-line {
white-space: pre;
cursor: default;
}

.segment-explorer-line-icon {
margin-right: 4px;
}
.segment-explorer-line-icon-page {
color: inherit;
.segment-explorer-line {
color: var(--color-gray-1000);
}

.segment-explorer-line-text-page {
color: var(--color-gray-1000);
font-weight: 500;
[data-nextjs-devtool-segment-explorer-level='odd'] {
background-color: var(--color-gray-100);
}
`
35 changes: 12 additions & 23 deletions test/development/app-dir/segment-explorer/segment-explorer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,35 +26,24 @@ describe('segment-explorer', () => {
it('should render the segment explorer for parallel routes', async () => {
const browser = await next.browser('/parallel-routes')
expect(await getSegmentExplorerContent(browser)).toMatchInlineSnapshot(`
"app/
layout.tsx
parallel-routes/
layout.tsx
page.tsx
@bar/
layout.tsx
page.tsx
@foo/
layout.tsx
page.tsx"
"applayout.tsx
parallel-routeslayout.tsx
parallel-routespage.tsx
@barlayout.tsx
@barpage.tsx
@foolayout.tsx
@foopage.tsx"
`)
})

it('should render the segment explorer for nested routes', async () => {
const browser = await next.browser('/blog/~/grid')
expect(await getSegmentExplorerContent(browser)).toMatchInlineSnapshot(`
"app/
layout.tsx
(v2)/
layout.tsx
blog/
(team)/
layout.tsx
~/
(overview)/
layout.tsx
grid/
page.tsx"
"applayout.tsx
(v2)layout.tsx
(team)layout.tsx
(overview)layout.tsx
gridpage.tsx"
`)
})
})
Loading