Skip to content

Commit 737d319

Browse files
authored
[segment-explorer] optimize tree view (#80392)
1 parent cce749a commit 737d319

File tree

2 files changed

+91
-141
lines changed

2 files changed

+91
-141
lines changed

packages/next/src/next-devtools/dev-overlay/components/overview/segment-explorer.tsx

Lines changed: 79 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,12 @@ import type { HTMLProps } from 'react'
22
import { css } from '../../utils/css'
33
import type { DevToolsInfoPropsCore } from '../errors/dev-tools-indicator/dev-tools-info/dev-tools-info'
44
import { DevToolsInfo } from '../errors/dev-tools-indicator/dev-tools-info/dev-tools-info'
5-
import { cx } from '../../utils/cx'
65
import {
76
type SegmentNode,
87
useSegmentTreeClientState,
98
} from '../../../../shared/lib/devtool/app-segment-tree'
109
import type { Trie, TrieNode } from '../../../../shared/lib/devtool/trie'
1110

12-
const IconLayout = (props: React.SVGProps<SVGSVGElement>) => {
13-
return (
14-
<svg
15-
{...props}
16-
viewBox="0 0 16 16"
17-
fill="none"
18-
xmlns="http://www.w3.org/2000/svg"
19-
>
20-
<path
21-
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"
22-
fill="currentColor"
23-
/>
24-
</svg>
25-
)
26-
}
27-
28-
const IconPage = (props: React.SVGProps<SVGSVGElement>) => {
29-
return (
30-
<svg
31-
{...props}
32-
viewBox="0 0 16 16"
33-
fill="none"
34-
strokeLinejoin="round"
35-
xmlns="http://www.w3.org/2000/svg"
36-
>
37-
<path
38-
fillRule="evenodd"
39-
clipRule="evenodd"
40-
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"
41-
fill="currentColor"
42-
/>
43-
</svg>
44-
)
45-
}
46-
47-
const ICONS = {
48-
layout: <IconLayout width={16} />,
49-
page: <IconPage width={16} />,
50-
}
51-
5211
function PageSegmentTree({ tree }: { tree: Trie<SegmentNode> | undefined }) {
5312
if (!tree) {
5413
return null
@@ -63,6 +22,7 @@ function PageSegmentTree({ tree }: { tree: Trie<SegmentNode> | undefined }) {
6322
node={tree.getRoot()}
6423
level={0}
6524
segment=""
25+
parentSegment=""
6626
/>
6727
</div>
6828
)
@@ -71,19 +31,22 @@ function PageSegmentTree({ tree }: { tree: Trie<SegmentNode> | undefined }) {
7131
function PageSegmentTreeLayerPresentation({
7232
tree,
7333
segment,
34+
parentSegment,
7435
node,
7536
level,
7637
}: {
7738
tree: Trie<SegmentNode>
7839
segment: string
40+
parentSegment: string
7941
node: TrieNode<SegmentNode>
8042
level: number
8143
}) {
8244
const pagePath = node.value?.pagePath || ''
8345
const nodeName = node.value?.type
46+
const isFile = !!nodeName
8447

8548
const segments = pagePath.split('/') || []
86-
const fileName = segments.pop() || ''
49+
const fileName = isFile ? segments.pop() || '' : ''
8750
const childrenKeys = Object.keys(node.children)
8851

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

63+
// check if it has file children
64+
const hasFileChildren = sortedChildrenKeys.some((key) => {
65+
const childNode = node.children[key]
66+
return !!childNode?.value?.type
67+
})
68+
69+
// If it's the 1st level and contains a file, use 'app' as the folder name
70+
const folderName = level === 1 && isFile ? 'app' : parentSegment
71+
10072
return (
101-
<div
102-
className="segment-explorer-item"
103-
data-nextjs-devtool-segment-explorer-segment={segment}
104-
>
105-
{fileName ? (
106-
<div className={cx('segment-explorer-item-row')}>
107-
<div className="segment-explorer-line">
108-
<div className={`segment-explorer-line-text-${nodeName}`}>
109-
<span
110-
className={cx(
111-
'segment-explorer-line-icon',
112-
`segment-explorer-line-icon-${nodeName}`
113-
)}
114-
>
115-
{nodeName === 'layout' ? ICONS.layout : ICONS.page}
116-
</span>
117-
<span className="segment-explorer-filename-path">{fileName}</span>
118-
</div>
119-
</div>
120-
</div>
121-
) : segment ? (
122-
<div className={'segment-explorer-item-row'}>
123-
<div className="segment-explorer-line">
124-
<div className={`segment-explorer-line-text-${nodeName}`}>
125-
<span
126-
className={cx(
127-
'segment-explorer-line-icon',
128-
`segment-explorer-line-icon-${nodeName}`
129-
)}
130-
></span>
131-
<span className="segment-explorer-filename-path">
132-
{`${segment}/`}
133-
</span>
73+
<>
74+
{isFile ? (
75+
<div
76+
className="segment-explorer-item"
77+
data-nextjs-devtool-segment-explorer-segment={segment}
78+
>
79+
<div
80+
className="segment-explorer-item-row"
81+
style={{
82+
// If it's children levels, show indents if there's any file at that level.
83+
// Otherwise it's empty folder, no need to show indents.
84+
...(level > 0 && isFile && { paddingLeft: `${level * 8}px` }),
85+
}}
86+
>
87+
<div className="segment-explorer-line">
88+
<div className={`segment-explorer-line-text-${nodeName}`}>
89+
<div className="segment-explorer-filename">
90+
{folderName && (
91+
<span className="segment-explorer-filename--path">
92+
{folderName}
93+
</span>
94+
)}
95+
<span className="segment-explorer-filename--name">
96+
{fileName}
97+
</span>
98+
</div>
99+
</div>
134100
</div>
135101
</div>
136102
</div>
137103
) : null}
138-
139-
<div
140-
className="segment-explorer-segment-children"
141-
data-nextjs-devtool-segment-explorer-level={level}
142-
>
143-
{sortedChildrenKeys.map((childSegment) => {
144-
const child = node.children[childSegment]
145-
return (
146-
child && (
147-
<PageSegmentTreeLayerPresentation
148-
key={childSegment}
149-
segment={childSegment}
150-
tree={tree}
151-
node={child}
152-
level={level + 1}
153-
/>
154-
)
155-
)
156-
})}
157-
</div>
158-
</div>
104+
{sortedChildrenKeys.map((childSegment) => {
105+
const child = node.children[childSegment]
106+
if (!child) {
107+
return null
108+
}
109+
return (
110+
<PageSegmentTreeLayerPresentation
111+
key={childSegment}
112+
segment={childSegment}
113+
parentSegment={segment}
114+
tree={tree}
115+
node={child}
116+
level={hasFileChildren ? level + 1 : level}
117+
/>
118+
)
119+
})}
120+
</>
159121
)
160122
}
161123

@@ -177,47 +139,46 @@ export function SegmentsExplorer(
177139
export const DEV_TOOLS_INFO_RENDER_FILES_STYLES = css`
178140
.segment-explorer-content {
179141
overflow-y: auto;
180-
padding: 0 12px;
181142
font-size: var(--size-14);
143+
margin: -12px -8px;
144+
}
145+
146+
.segment-explorer-item {
147+
margin: 4px 0;
148+
}
149+
150+
.segment-explorer-item:nth-child(odd) {
151+
background-color: var(--color-gray-100);
182152
}
183153
184154
.segment-explorer-item-row {
185155
display: flex;
186156
align-items: center;
187-
gap: 8px;
188-
padding: 2px 0;
157+
padding: 4px 24px;
158+
border-radius: 6px;
189159
}
190160
191-
[data-nextjs-devtool-segment-explorer-level].segment-explorer-segment-children {
192-
padding-left: 20px;
193-
}
194-
[data-nextjs-devtool-segment-explorer-level='0'].segment-explorer-segment-children {
195-
padding-left: 0px;
161+
.segment-explorer-children--intended {
162+
padding-left: 16px;
196163
}
197164
198-
.segment-explorer-filename-path {
199-
display: inline-block;
165+
.segment-explorer-filename--path {
166+
margin-right: 8px;
200167
}
201-
202-
.segment-explorer-filename-path a {
203-
color: inherit;
204-
text-decoration: inherit;
168+
.segment-explorer-filename--name {
169+
color: var(--color-gray-800);
205170
}
206171
207172
.segment-explorer-line {
208173
white-space: pre;
209174
cursor: default;
210175
}
211176
212-
.segment-explorer-line-icon {
213-
margin-right: 4px;
214-
}
215-
.segment-explorer-line-icon-page {
216-
color: inherit;
177+
.segment-explorer-line {
178+
color: var(--color-gray-1000);
217179
}
218180
219-
.segment-explorer-line-text-page {
220-
color: var(--color-gray-1000);
221-
font-weight: 500;
181+
[data-nextjs-devtool-segment-explorer-level='odd'] {
182+
background-color: var(--color-gray-100);
222183
}
223184
`

test/development/app-dir/segment-explorer/segment-explorer.test.ts

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,35 +26,24 @@ describe('segment-explorer', () => {
2626
it('should render the segment explorer for parallel routes', async () => {
2727
const browser = await next.browser('/parallel-routes')
2828
expect(await getSegmentExplorerContent(browser)).toMatchInlineSnapshot(`
29-
"app/
30-
layout.tsx
31-
parallel-routes/
32-
layout.tsx
33-
page.tsx
34-
@bar/
35-
layout.tsx
36-
page.tsx
37-
@foo/
38-
layout.tsx
39-
page.tsx"
29+
"applayout.tsx
30+
parallel-routeslayout.tsx
31+
parallel-routespage.tsx
32+
@barlayout.tsx
33+
@barpage.tsx
34+
@foolayout.tsx
35+
@foopage.tsx"
4036
`)
4137
})
4238

4339
it('should render the segment explorer for nested routes', async () => {
4440
const browser = await next.browser('/blog/~/grid')
4541
expect(await getSegmentExplorerContent(browser)).toMatchInlineSnapshot(`
46-
"app/
47-
layout.tsx
48-
(v2)/
49-
layout.tsx
50-
blog/
51-
(team)/
52-
layout.tsx
53-
~/
54-
(overview)/
55-
layout.tsx
56-
grid/
57-
page.tsx"
42+
"applayout.tsx
43+
(v2)layout.tsx
44+
(team)layout.tsx
45+
(overview)layout.tsx
46+
gridpage.tsx"
5847
`)
5948
})
6049
})

0 commit comments

Comments
 (0)