Skip to content

Commit a054381

Browse files
konpeki622Akryum
andauthored
feat: shift + click to expand all child components (vuejs#1752)
Co-authored-by: Guillaume Chau <[email protected]>
1 parent b1924d7 commit a054381

File tree

12 files changed

+63
-33
lines changed

12 files changed

+63
-33
lines changed

packages/api/src/api/component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface ComponentTreeNode {
1717
isRouterView?: boolean
1818
macthedRouteSegment?: string
1919
tags: InspectorNodeTag[]
20+
autoOpen: boolean
2021
meta?: any
2122
}
2223

packages/api/src/api/hooks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export type HookPayloads = {
5656
componentTreeData: ComponentTreeNode[]
5757
maxDepth: number
5858
filter: string
59+
recursively: boolean
5960
}
6061
[Hooks.VISIT_COMPONENT_TREE]: {
6162
app: App

packages/app-backend-api/src/api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,13 @@ export class DevtoolsApi {
8888
})
8989
}
9090

91-
async walkComponentTree (instance: ComponentInstance, maxDepth = -1, filter: string = null) {
91+
async walkComponentTree (instance: ComponentInstance, maxDepth = -1, filter: string = null, recursively = false) {
9292
const payload = await this.callHook(Hooks.WALK_COMPONENT_TREE, {
9393
componentInstance: instance,
9494
componentTreeData: null,
9595
maxDepth,
9696
filter,
97+
recursively,
9798
})
9899
return payload.componentTreeData
99100
}

packages/app-backend-core/src/component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { App, ComponentInstance, EditStatePayload, now } from '@vue/devtools-api
66
const MAX_$VM = 10
77
const $vmQueue = []
88

9-
export async function sendComponentTreeData (appRecord: AppRecord, instanceId: string, filter = '', maxDepth: number = null, ctx: BackendContext) {
9+
export async function sendComponentTreeData (appRecord: AppRecord, instanceId: string, filter = '', maxDepth: number = null, recursively = false, ctx: BackendContext) {
1010
if (!instanceId || appRecord !== ctx.currentAppRecord) return
1111

1212
// Flush will send all components in the tree
@@ -30,7 +30,7 @@ export async function sendComponentTreeData (appRecord: AppRecord, instanceId: s
3030
if (maxDepth == null) {
3131
maxDepth = instance === ctx.currentAppRecord.rootInstance ? 2 : 1
3232
}
33-
const data = await appRecord.backend.api.walkComponentTree(instance, maxDepth, filter)
33+
const data = await appRecord.backend.api.walkComponentTree(instance, maxDepth, filter, recursively)
3434
const payload = {
3535
instanceId,
3636
treeData: stringify(data),
@@ -128,7 +128,7 @@ export function getComponentInstance (appRecord: AppRecord, instanceId: string,
128128

129129
export async function refreshComponentTreeSearch (ctx: BackendContext) {
130130
if (!ctx.currentAppRecord.componentFilter) return
131-
await sendComponentTreeData(ctx.currentAppRecord, '_root', ctx.currentAppRecord.componentFilter, null, ctx)
131+
await sendComponentTreeData(ctx.currentAppRecord, '_root', ctx.currentAppRecord.componentFilter, null, false, ctx)
132132
}
133133

134134
export async function sendComponentUpdateTracking (instanceId: string, ctx: BackendContext) {

packages/app-backend-core/src/index.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ async function connect () {
125125

126126
// Update tree (tags)
127127
if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, sub => sub.payload.instanceId === id)) {
128-
await sendComponentTreeData(appRecord, id, appRecord.componentFilter, 0, ctx)
128+
await sendComponentTreeData(appRecord, id, appRecord.componentFilter, 0, false, ctx)
129129
}
130130
} catch (e) {
131131
if (SharedData.debugInfo) {
@@ -185,7 +185,7 @@ async function connect () {
185185
const parentId = await getComponentId(app, parentUid, parentInstances[i], ctx)
186186
if (i < 2 && isSubscribed(BridgeSubscriptions.COMPONENT_TREE, sub => sub.payload.instanceId === parentId)) {
187187
raf(() => {
188-
sendComponentTreeData(appRecord, parentId, appRecord.componentFilter, null, ctx)
188+
sendComponentTreeData(appRecord, parentId, appRecord.componentFilter, null, false, ctx)
189189
})
190190
}
191191

@@ -227,7 +227,7 @@ async function connect () {
227227
if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, sub => sub.payload.instanceId === parentId)) {
228228
raf(async () => {
229229
try {
230-
sendComponentTreeData(await getAppRecord(app, ctx), parentId, appRecord.componentFilter, null, ctx)
230+
sendComponentTreeData(await getAppRecord(app, ctx), parentId, appRecord.componentFilter, null, false, ctx)
231231
} catch (e) {
232232
if (SharedData.debugInfo) {
233233
console.error(e)
@@ -365,7 +365,7 @@ async function connect () {
365365

366366
const handleFlush = debounce(async () => {
367367
if (ctx.currentAppRecord?.backend.options.features.includes(BuiltinBackendFeature.FLUSH)) {
368-
await sendComponentTreeData(ctx.currentAppRecord, '_root', ctx.currentAppRecord.componentFilter, null, ctx)
368+
await sendComponentTreeData(ctx.currentAppRecord, '_root', ctx.currentAppRecord.componentFilter, null, false, ctx)
369369
if (ctx.currentInspectedComponentId) {
370370
await sendSelectedComponentData(ctx.currentAppRecord, ctx.currentInspectedComponentId, ctx)
371371
}
@@ -433,10 +433,10 @@ function connectBridge () {
433433

434434
// Components
435435

436-
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_TREE, async ({ instanceId, filter }) => {
436+
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_TREE, async ({ instanceId, filter, recursively }) => {
437437
ctx.currentAppRecord.componentFilter = filter
438438
subscribe(BridgeSubscriptions.COMPONENT_TREE, { instanceId })
439-
await sendComponentTreeData(ctx.currentAppRecord, instanceId, filter, null, ctx)
439+
await sendComponentTreeData(ctx.currentAppRecord, instanceId, filter, null, recursively, ctx)
440440
})
441441

442442
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_SELECTED_DATA, async (instanceId) => {

packages/app-backend-core/src/perf.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ export async function performanceMarkEnd (
145145
const id = await getComponentId(app, uid, instance, ctx)
146146
if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, sub => sub.payload.instanceId === id)) {
147147
raf(() => {
148-
sendComponentTreeData(appRecord, id, ctx.currentAppRecord.componentFilter, null, ctx)
148+
sendComponentTreeData(appRecord, id, ctx.currentAppRecord.componentFilter, null, false, ctx)
149149
})
150150
}
151151
}

packages/app-backend-vue2/src/components/tree.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@ let api: DevtoolsApi
1515
const consoleBoundInstances = Array(5)
1616

1717
let filter = ''
18+
let recursively = false
1819
const functionalIds = new Map()
1920

2021
// Dedupe instances
2122
// Some instances may be both on a component and on a child abstract/functional component
2223
const captureIds = new Map()
2324

24-
export async function walkTree (instance, pFilter: string, api: DevtoolsApi, ctx: BackendContext): Promise<ComponentTreeNode[]> {
25+
export async function walkTree (instance, pFilter: string, pRecursively: boolean, api: DevtoolsApi, ctx: BackendContext): Promise<ComponentTreeNode[]> {
2526
initCtx(api, ctx)
2627
filter = pFilter
28+
recursively = pRecursively
2729
functionalIds.clear()
2830
captureIds.clear()
2931
const result: ComponentTreeNode[] = flatten(await findQualifiedChildren(instance))
@@ -215,6 +217,7 @@ async function capture (instance, index?: number, list?: any[]): Promise<Compone
215217
hasChildren: !!children.length,
216218
inactive: false,
217219
isFragment: false, // TODO: Check what is it for.
220+
autoOpen: recursively,
218221
}
219222
return api.visitComponentTree(
220223
instance,
@@ -251,6 +254,7 @@ async function capture (instance, index?: number, list?: any[]): Promise<Compone
251254
isFragment: !!instance._isFragment,
252255
children,
253256
hasChildren: !!children.length,
257+
autoOpen: recursively,
254258
tags: [],
255259
meta: {},
256260
}

packages/app-backend-vue2/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const backend = defineBackend({
2929
})
3030

3131
api.on.walkComponentTree(async (payload, ctx) => {
32-
payload.componentTreeData = await walkTree(payload.componentInstance, payload.filter, api, ctx)
32+
payload.componentTreeData = await walkTree(payload.componentInstance, payload.filter, payload.recursively, api, ctx)
3333
})
3434

3535
api.on.walkComponentParents((payload, ctx) => {

packages/app-backend-vue3/src/components/tree.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ export class ComponentWalker {
88
ctx: BackendContext
99
api: DevtoolsApi
1010
maxDepth: number
11+
recursively: boolean
1112
componentFilter: ComponentFilter
1213
// Dedupe instances
1314
// Some instances may be both on a component and on a child abstract/functional component
1415
captureIds: Map<string, undefined>
1516

16-
constructor (maxDepth: number, filter: string, api: DevtoolsApi, ctx: BackendContext) {
17+
constructor (maxDepth: number, filter: string, recursively: boolean, api: DevtoolsApi, ctx: BackendContext) {
1718
this.ctx = ctx
1819
this.api = api
1920
this.maxDepth = maxDepth
21+
this.recursively = recursively
2022
this.componentFilter = new ComponentFilter(filter)
2123
}
2224

@@ -161,6 +163,7 @@ export class ComponentWalker {
161163
backgroundColor: 0xeeeeee,
162164
},
163165
],
166+
autoOpen: this.recursively,
164167
}
165168

166169
// capture children

packages/app-backend-vue3/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ export const backend = defineBackend({
2626
})
2727

2828
api.on.walkComponentTree(async (payload, ctx) => {
29-
const walker = new ComponentWalker(payload.maxDepth, payload.filter, api, ctx)
29+
const walker = new ComponentWalker(payload.maxDepth, payload.filter, payload.recursively, api, ctx)
3030
payload.componentTreeData = await walker.getComponentTree(payload.componentInstance)
3131
})
3232

3333
api.on.walkComponentParents((payload, ctx) => {
34-
const walker = new ComponentWalker(0, null, api, ctx)
34+
const walker = new ComponentWalker(0, null, false, api, ctx)
3535
payload.parentInstances = walker.getComponentParents(payload.componentInstance)
3636
})
3737

packages/app-frontend/src/features/components/ComponentTreeNode.vue

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ export default defineComponent({
141141
}
142142
}
143143
144+
function switchToggle (event: MouseEvent) {
145+
if (event.shiftKey) {
146+
toggle(true, !expanded.value)
147+
} else {
148+
toggle()
149+
}
150+
}
144151
// Update tracking
145152
146153
const updateTracking = computed(() => updateTrackingEvents.value[props.instance.id])
@@ -157,7 +164,7 @@ export default defineComponent({
157164
selected,
158165
select,
159166
expanded,
160-
toggle,
167+
switchToggle,
161168
highlight,
162169
unhighlight,
163170
selectNextSibling,
@@ -194,7 +201,7 @@ export default defineComponent({
194201
:class="{
195202
'invisible': !instance.hasChildren
196203
}"
197-
@click.stop="toggle()"
204+
@click.stop="switchToggle"
198205
>
199206
<span
200207
:class="{

packages/app-frontend/src/features/components/composable/components.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,21 @@ export function useComponent (instance: Ref<ComponentTreeNode>) {
135135
const isExpanded = computed(() => isComponentOpen(instance.value.id))
136136
const isExpandedUndefined = computed(() => expandedMap.value[instance.value.id] == null)
137137

138-
function toggleExpand () {
139-
if (!instance.value.hasChildren) return
140-
setComponentOpen(instance.value.id, !isExpanded.value)
141-
if (isComponentOpen(instance.value.id)) {
142-
requestComponentTree(instance.value.id)
138+
function toggleExpand (recursively = false, value?, child?) {
139+
const treeNode = child || instance.value
140+
if (!treeNode.hasChildren) return
141+
const isOpen = value === undefined ? !isExpanded.value : value
142+
setComponentOpen(treeNode.id, isOpen)
143+
if (isComponentOpen(treeNode.id)) {
144+
requestComponentTree(treeNode.id, recursively)
145+
} else {
146+
// stop expanding all treenode
147+
treeNode.autoOpen = false
148+
}
149+
if (recursively) {
150+
treeNode.children.forEach(child => {
151+
toggleExpand(recursively, value, child)
152+
})
143153
}
144154
}
145155

@@ -168,7 +178,9 @@ export function useComponent (instance: Ref<ComponentTreeNode>) {
168178
}
169179

170180
onMounted(() => {
171-
if (isExpanded.value) {
181+
if (instance.value.autoOpen) {
182+
toggleExpand(true, true)
183+
} else if (isExpanded.value) {
172184
requestComponentTree(instance.value.id)
173185
}
174186
})
@@ -266,7 +278,7 @@ export const requestedComponentTree = new Set()
266278

267279
let requestComponentTreeRetryDelay = 500
268280

269-
export async function requestComponentTree (instanceId: ComponentTreeNode['id'] = null) {
281+
export async function requestComponentTree (instanceId: ComponentTreeNode['id'] = null, recursively = false) {
270282
if (!instanceId) {
271283
instanceId = '_root'
272284
}
@@ -278,29 +290,30 @@ export async function requestComponentTree (instanceId: ComponentTreeNode['id']
278290

279291
await waitForAppSelect()
280292

281-
_sendTreeRequest(instanceId)
282-
_queueRetryTree(instanceId)
293+
_sendTreeRequest(instanceId, recursively)
294+
_queueRetryTree(instanceId, recursively)
283295
}
284296

285-
function _sendTreeRequest (instanceId: ComponentTreeNode['id']) {
297+
function _sendTreeRequest (instanceId: ComponentTreeNode['id'], recursively = false) {
286298
getBridge().send(BridgeEvents.TO_BACK_COMPONENT_TREE, {
287299
instanceId,
288300
filter: treeFilter.value,
301+
recursively,
289302
})
290303
}
291304

292-
function _queueRetryTree (instanceId: ComponentTreeNode['id']) {
293-
setTimeout(() => _retryRequestComponentTree(instanceId), requestComponentTreeRetryDelay)
305+
function _queueRetryTree (instanceId: ComponentTreeNode['id'], recursively = false) {
306+
setTimeout(() => _retryRequestComponentTree(instanceId, recursively), requestComponentTreeRetryDelay)
294307
requestComponentTreeRetryDelay *= 1.5
295308
}
296309

297-
function _retryRequestComponentTree (instanceId: ComponentTreeNode['id']) {
310+
function _retryRequestComponentTree (instanceId: ComponentTreeNode['id'], recursively = false) {
298311
if (rootInstances.value.length) {
299312
requestComponentTreeRetryDelay = 500
300313
return
301314
}
302-
_sendTreeRequest(instanceId)
303-
_queueRetryTree(instanceId)
315+
_sendTreeRequest(instanceId, recursively)
316+
_queueRetryTree(instanceId, recursively)
304317
}
305318

306319
export function ensureComponentsMapData (data: ComponentTreeNode) {

0 commit comments

Comments
 (0)