Skip to content

Commit dc6df24

Browse files
committed
[segment explorer] capture defined boundaries
1 parent 81f0c76 commit dc6df24

File tree

8 files changed

+160
-28
lines changed

8 files changed

+160
-28
lines changed

packages/next/src/client/components/layout-router.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ export default function OuterLayoutRouter({
507507
forbidden,
508508
unauthorized,
509509
gracefullyDegrade,
510+
segmentViewBoundaries,
510511
}: {
511512
parallelRouterKey: string
512513
error: ErrorComponent | undefined
@@ -519,6 +520,7 @@ export default function OuterLayoutRouter({
519520
forbidden: React.ReactNode | undefined
520521
unauthorized: React.ReactNode | undefined
521522
gracefullyDegrade?: boolean
523+
segmentViewBoundaries?: React.ReactNode
522524
}) {
523525
const context = useContext(LayoutRouterContext)
524526
if (!context) {
@@ -659,13 +661,14 @@ export default function OuterLayoutRouter({
659661
)
660662

661663
if (process.env.NODE_ENV !== 'production') {
662-
const SegmentStateProvider = (
664+
const { SegmentStateProvider } =
663665
require('../../next-devtools/userspace/app/segment-explorer-node') as typeof import('../../next-devtools/userspace/app/segment-explorer-node')
664-
)
665-
.SegmentStateProvider as typeof import('../../next-devtools/userspace/app/segment-explorer-node').SegmentStateProvider as typeof import('../../next-devtools/userspace/app/segment-explorer-node').SegmentStateProvider
666666

667667
child = (
668-
<SegmentStateProvider key={stateKey}>{child}</SegmentStateProvider>
668+
<SegmentStateProvider key={stateKey}>
669+
{child}
670+
{segmentViewBoundaries}
671+
</SegmentStateProvider>
669672
)
670673
}
671674

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

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import type { SegmentNodeState } from '../../../userspace/app/segment-explorer-n
55
export function SegmentBoundaryTrigger({
66
onSelectBoundary,
77
offset,
8+
boundaries,
89
}: {
910
onSelectBoundary: SegmentNodeState['setBoundaryType']
1011
offset: number
12+
boundaries: Record<'not-found' | 'loading' | 'error', string | null>
1113
}) {
1214
const [shadowRoot] = useState<ShadowRoot>(() => {
1315
const ownerDocument = document
@@ -17,9 +19,24 @@ export function SegmentBoundaryTrigger({
1719
const shadowRootRef = useRef<ShadowRoot>(shadowRoot)
1820

1921
const triggerOptions = [
20-
{ label: 'Trigger Loading', value: 'loading', icon: <LoadingIcon /> },
21-
{ label: 'Trigger Error', value: 'error', icon: <ErrorIcon /> },
22-
{ label: 'Trigger Not Found', value: 'not-found', icon: <NotFoundIcon /> },
22+
{
23+
label: 'Trigger Loading',
24+
value: 'loading',
25+
icon: <LoadingIcon />,
26+
disabled: !boundaries.loading,
27+
},
28+
{
29+
label: 'Trigger Error',
30+
value: 'error',
31+
icon: <ErrorIcon />,
32+
disabled: !boundaries.error,
33+
},
34+
{
35+
label: 'Trigger Not Found',
36+
value: 'not-found',
37+
icon: <NotFoundIcon />,
38+
disabled: !boundaries['not-found'],
39+
},
2340
]
2441

2542
const resetOption = {
@@ -74,6 +91,7 @@ export function SegmentBoundaryTrigger({
7491
key={option.value}
7592
className="segment-boundary-dropdown-item"
7693
onClick={() => handleSelect(option.value)}
94+
disabled={option.disabled}
7795
>
7896
{option.icon}
7997
{option.label}
@@ -274,9 +292,14 @@ export const styles = `
274292
width: 100%;
275293
}
276294
295+
.segment-boundary-dropdown-item[data-disabled] {
296+
color: var(--color-gray-400);
297+
cursor: not-allowed;
298+
}
299+
277300
.segment-boundary-dropdown-item svg {
278301
margin-right: 12px;
279-
color: var(--color-gray-900);
302+
color: currentColor;
280303
}
281304
282305
.segment-boundary-dropdown-item:hover {

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,23 @@ function PageSegmentTreeLayerPresentation({
133133
}
134134

135135
const hasFilesChildren = filesChildrenKeys.length > 0
136+
const boundaries: Record<'not-found' | 'loading' | 'error', string | null> = {
137+
'not-found': null,
138+
loading: null,
139+
error: null,
140+
}
141+
142+
filesChildrenKeys.forEach((childKey) => {
143+
const childNode = node.children[childKey]
144+
if (!childNode || !childNode.value) return
145+
if (childNode.value.type.startsWith('boundary:')) {
146+
const boundaryType = childNode.value.type.split(':')[1] as
147+
| 'not-found'
148+
| 'loading'
149+
| 'error'
150+
boundaries[boundaryType] = childNode.value.pagePath || null
151+
}
152+
})
136153

137154
return (
138155
<>
@@ -165,6 +182,11 @@ function PageSegmentTreeLayerPresentation({
165182
if (!childNode || !childNode.value) {
166183
return null
167184
}
185+
// If it's boundary node, which marks the existence of the boundary not the rendered status,
186+
// we don't need to present in the rendered files.
187+
if (childNode.value.type.startsWith('boundary:')) {
188+
return null
189+
}
168190
const filePath = childNode.value.pagePath
169191
const lastSegment = filePath.split('/').pop() || ''
170192
const isBuiltin = filePath.startsWith(BUILTIN_PREFIX)
@@ -208,6 +230,7 @@ function PageSegmentTreeLayerPresentation({
208230
<SegmentBoundaryTrigger
209231
offset={6}
210232
onSelectBoundary={pageChild.value.setBoundaryType}
233+
boundaries={boundaries}
211234
/>
212235
)}
213236
</div>

packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.test.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const createSegmentNode = ({
1919
type,
2020
pagePath,
2121
boundaryType: null,
22+
boundaries: { notFound: false, loading: false, error: false },
2223
setBoundaryType: placeholder,
2324
}
2425
}
@@ -73,6 +74,11 @@ describe('Segment Explorer', () => {
7374
pagePath: '/a/layout.js',
7475
type: 'layout',
7576
boundaryType: null,
77+
boundaries: {
78+
notFound: false,
79+
loading: false,
80+
error: false,
81+
},
7682
setBoundaryType: expect.anything(),
7783
},
7884
},
@@ -82,6 +88,11 @@ describe('Segment Explorer', () => {
8288
pagePath: '/a/page.js',
8389
type: 'page',
8490
boundaryType: null,
91+
boundaries: {
92+
notFound: false,
93+
loading: false,
94+
error: false,
95+
},
8596
setBoundaryType: expect.anything(),
8697
},
8798
},
@@ -94,6 +105,7 @@ describe('Segment Explorer', () => {
94105
pagePath: '/layout.js',
95106
type: 'layout',
96107
boundaryType: null,
108+
boundaries: { notFound: false, loading: false, error: false },
97109
setBoundaryType: expect.anything(),
98110
},
99111
},
@@ -140,6 +152,11 @@ describe('Segment Explorer', () => {
140152
pagePath: '/a/b/@sidebar/page.js',
141153
type: 'page',
142154
boundaryType: null,
155+
boundaries: {
156+
notFound: false,
157+
loading: false,
158+
error: false,
159+
},
143160
setBoundaryType: expect.anything(),
144161
},
145162
},
@@ -152,6 +169,11 @@ describe('Segment Explorer', () => {
152169
pagePath: '/a/b/layout.js',
153170
type: 'layout',
154171
boundaryType: null,
172+
boundaries: {
173+
notFound: false,
174+
loading: false,
175+
error: false,
176+
},
155177
setBoundaryType: expect.anything(),
156178
},
157179
},
@@ -161,6 +183,11 @@ describe('Segment Explorer', () => {
161183
pagePath: '/a/b/page.js',
162184
type: 'page',
163185
boundaryType: null,
186+
boundaries: {
187+
notFound: false,
188+
loading: false,
189+
error: false,
190+
},
164191
setBoundaryType: expect.anything(),
165192
},
166193
},
@@ -173,6 +200,11 @@ describe('Segment Explorer', () => {
173200
pagePath: '/a/layout.js',
174201
type: 'layout',
175202
boundaryType: null,
203+
boundaries: {
204+
notFound: false,
205+
loading: false,
206+
error: false,
207+
},
176208
setBoundaryType: expect.anything(),
177209
},
178210
},
@@ -185,6 +217,7 @@ describe('Segment Explorer', () => {
185217
pagePath: '/layout.js',
186218
type: 'layout',
187219
boundaryType: null,
220+
boundaries: { notFound: false, loading: false, error: false },
188221
setBoundaryType: expect.anything(),
189222
},
190223
},
@@ -215,6 +248,11 @@ describe('Segment Explorer', () => {
215248
pagePath: '/a/b/@sidebar/page.js',
216249
type: 'page',
217250
boundaryType: null,
251+
boundaries: {
252+
notFound: false,
253+
loading: false,
254+
error: false,
255+
},
218256
setBoundaryType: expect.anything(),
219257
},
220258
},
@@ -227,6 +265,11 @@ describe('Segment Explorer', () => {
227265
pagePath: '/a/b/page.js',
228266
type: 'page',
229267
boundaryType: null,
268+
boundaries: {
269+
notFound: false,
270+
loading: false,
271+
error: false,
272+
},
230273
setBoundaryType: expect.anything(),
231274
},
232275
},
@@ -239,6 +282,11 @@ describe('Segment Explorer', () => {
239282
pagePath: '/a/layout.js',
240283
type: 'layout',
241284
boundaryType: null,
285+
boundaries: {
286+
notFound: false,
287+
loading: false,
288+
error: false,
289+
},
242290
setBoundaryType: expect.anything(),
243291
},
244292
},
@@ -251,6 +299,7 @@ describe('Segment Explorer', () => {
251299
pagePath: '/layout.js',
252300
type: 'layout',
253301
boundaryType: null,
302+
boundaries: { notFound: false, loading: false, error: false },
254303
setBoundaryType: expect.anything(),
255304
},
256305
},

packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ function createTrie<Value = string>({
9797
function remove(value: Value) {
9898
let currentNode = root
9999
const segments = getCharacters(value)
100+
100101
const stack: TrieNode<Value>[] = []
101102
let found = true
102103
for (const segment of segments) {

packages/next/src/next-devtools/userspace/app/segment-explorer-node.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,14 @@ function SegmentTrieNode({
3131
pagePath: string
3232
}): React.ReactNode {
3333
const { boundaryType, setBoundaryType } = useSegmentState()
34-
const nodeState: SegmentNodeState = useMemo(
35-
() => ({
34+
const nodeState: SegmentNodeState = useMemo(() => {
35+
return {
3636
type,
3737
pagePath,
3838
boundaryType,
3939
setBoundaryType,
40-
}),
41-
[type, pagePath, boundaryType, setBoundaryType]
42-
)
40+
}
41+
}, [type, pagePath, boundaryType, setBoundaryType])
4342

4443
// Use `useLayoutEffect` to ensure the state is updated during suspense.
4544
// `useEffect` won't work as the state is preserved during suspense.
@@ -143,7 +142,10 @@ export function SegmentStateProvider({ children }: { children: ReactNode }) {
143142
return (
144143
<SegmentStateContext.Provider
145144
key={errorBoundaryKey}
146-
value={{ boundaryType, setBoundaryType: setBoundaryTypeAndReload }}
145+
value={{
146+
boundaryType,
147+
setBoundaryType: setBoundaryTypeAndReload,
148+
}}
147149
>
148150
{children}
149151
</SegmentStateContext.Provider>

0 commit comments

Comments
 (0)