Skip to content

Commit dce7c1e

Browse files
feat: filter tree/states for custom inspectors (#664)
1 parent b478639 commit dce7c1e

File tree

10 files changed

+128
-52
lines changed

10 files changed

+128
-52
lines changed

packages/applet/src/composables/custom-inspector-state.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ type CustomInspectorState = Partial<{
88
label: string
99
logo: string
1010
timelineLayerIds: string[]
11+
treeFilterPlaceholder: string
12+
stateFilterPlaceholder: string
1113
}>
1214

1315
const VueDevToolsStateSymbol: InjectionKey<Ref<CustomInspectorState>> = Symbol.for('VueDevToolsCustomInspectorStateSymbol')

packages/applet/src/modules/components/index.vue

+10-23
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import RootStateViewer from '~/components/state/RootStateViewer.vue'
1717
import ComponentTree from '~/components/tree/TreeViewer.vue'
1818
import { createSelectedContext } from '~/composables/select'
1919
import { createExpandedContext } from '~/composables/toggle-expanded'
20-
import { searchDeepInObject } from '~/utils'
20+
import { filterInspectorState } from '~/utils'
2121
import ComponentRenderCode from './components/RenderCode.vue'
2222
2323
const emit = defineEmits(['openInEditor', 'onInspectComponentStart', 'onInspectComponentEnd'])
@@ -104,27 +104,14 @@ const activeTreeNode = computed(() => {
104104
})
105105
const activeTreeNodeFilePath = computed(() => activeTreeNode.value?.file ?? '')
106106
107-
const filteredState = computed(() => {
108-
const result = {}
109-
for (const groupKey in activeComponentState.value) {
110-
const group = activeComponentState.value[groupKey]
111-
const groupFields = group.filter((el) => {
112-
try {
113-
return searchDeepInObject({
114-
[el.key]: el.value,
115-
}, filterStateName.value)
116-
}
117-
catch (e) {
118-
return {
119-
[el.key]: e,
120-
}
121-
}
122-
})
123-
const normalized = flatten(Object.values(groupBy(sortByKey(groupFields), 'stateType')))
124-
if (groupFields.length)
125-
result[groupKey] = normalized
126-
}
127-
return result
107+
const displayState = computed(() => {
108+
return filterInspectorState({
109+
state: activeComponentState.value,
110+
filterKey: filterStateName.value,
111+
processGroup(groupFields) {
112+
return flatten(Object.values(groupBy(sortByKey(groupFields), 'stateType')))
113+
},
114+
})
128115
})
129116
130117
const { expanded: expandedTreeNodes } = createExpandedContext()
@@ -357,7 +344,7 @@ function toggleApp(id: string) {
357344
<i v-if="activeTreeNodeFilePath" v-tooltip.bottom="'Open in Editor'" class="i-carbon-launch h-4 w-4 cursor-pointer hover:(op-70)" @click="openInEditor" />
358345
</div>
359346
</div>
360-
<RootStateViewer class="no-scrollbar flex-1 select-none overflow-scroll" :data="filteredState" :node-id="activeComponentId" :inspector-id="inspectorId" expanded-state-id="component-state" />
347+
<RootStateViewer class="no-scrollbar flex-1 select-none overflow-scroll" :data="displayState" :node-id="activeComponentId" :inspector-id="inspectorId" expanded-state-id="component-state" />
361348
</div>
362349
<ComponentRenderCode v-if="componentRenderCodeVisible && componentRenderCode" :code="componentRenderCode" @close="closeComponentRenderCode" />
363350
</Pane>

packages/applet/src/modules/custom-inspector/components/state/Index.vue

+38-14
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import type { CustomInspectorNode, CustomInspectorOptions, CustomInspectorState } from '@vue/devtools-kit'
33
import { DevToolsMessagingEvents, onRpcConnected, rpc } from '@vue/devtools-core'
44
import { parse } from '@vue/devtools-kit'
5-
import { vTooltip, VueIcIcon } from '@vue/devtools-ui'
5+
import { vTooltip, VueIcIcon, VueInput } from '@vue/devtools-ui'
66
import { until } from '@vueuse/core'
77
import { Pane, Splitpanes } from 'splitpanes'
88
import { computed, onUnmounted, ref, watch } from 'vue'
@@ -13,6 +13,7 @@ import RootStateViewer from '~/components/state/RootStateViewer.vue'
1313
import ComponentTree from '~/components/tree/TreeViewer.vue'
1414
import { useCustomInspectorState } from '~/composables/custom-inspector-state'
1515
import { createExpandedContext } from '~/composables/toggle-expanded'
16+
import { filterInspectorState } from '~/utils'
1617
1718
const { expanded: expandedTreeNodes } = createExpandedContext()
1819
const { expanded: expandedStateNodes } = createExpandedContext('custom-inspector-state')
@@ -32,6 +33,24 @@ const selected = ref('')
3233
const state = ref<Record<string, CustomInspectorState[]>>({})
3334
const emptyState = computed(() => !Object.keys(state.value).length)
3435
36+
const inspectorState = useCustomInspectorState()
37+
38+
const filterTreeKey = ref('')
39+
const filterStateKey = ref('')
40+
41+
watch(filterTreeKey, (value, oldValue) => {
42+
if (!value.trim().length && !oldValue.trim().length)
43+
return
44+
getInspectorTree(value)
45+
})
46+
47+
const displayState = computed(() => {
48+
return filterInspectorState({
49+
state: state.value,
50+
filterKey: filterStateKey.value,
51+
})
52+
})
53+
3554
// tree
3655
function dfs(node: { id: string, children?: { id: string }[] }, path: string[] = [], linkedList: string[][] = []) {
3756
path.push(node.id)
@@ -120,8 +139,8 @@ watch(selected, () => {
120139
getInspectorState(selected.value)
121140
})
122141
123-
const getInspectorTree = () => {
124-
rpc.value.getInspectorTree({ inspectorId: inspectorId.value, filter: '' }).then((_data) => {
142+
function getInspectorTree(filter = '') {
143+
rpc.value.getInspectorTree({ inspectorId: inspectorId.value, filter }).then((_data) => {
125144
const data = parse(_data!)
126145
tree.value = data
127146
if (!selected.value && data.length) {
@@ -132,7 +151,7 @@ const getInspectorTree = () => {
132151
})
133152
}
134153
135-
until(inspectorId).toBeTruthy().then(getInspectorTree)
154+
until(inspectorId).toBeTruthy().then(() => getInspectorTree())
136155
137156
function onInspectorTreeUpdated(_data: string) {
138157
const data = parse(_data) as {
@@ -179,41 +198,46 @@ onUnmounted(() => {
179198
<DevToolsHeader :doc-link="customInspectState.homepage!">
180199
<Navbar />
181200
</DevToolsHeader>
182-
<template v-if="tree.length">
201+
<Empty v-if="!tree.length && !filterTreeKey.trim().length">
202+
No Data
203+
</Empty>
204+
<template v-else>
183205
<Splitpanes class="flex-1 overflow-auto">
184206
<Pane border="r base" size="40" h-full>
185207
<div class="h-full flex flex-col p2">
186-
<div v-if="actions?.length" class="mb-1 flex justify-end pb-1" border="b dashed base">
187-
<div class="flex items-center gap-2 px-1">
208+
<div class="grid grid-cols-[1fr_auto] mb1 items-center gap2 pb1" border="b dashed base">
209+
<VueInput v-model="filterTreeKey" :placeholder="inspectorState.treeFilterPlaceholder" />
210+
<div v-if="actions?.length" class="flex items-center gap-2 px-1">
188211
<div v-for="(action, index) in actions" :key="index" v-tooltip.bottom-end="{ content: action.tooltip }" class="flex items-center gap1" @click="callAction(index)">
189212
<VueIcIcon :name="`baseline-${action.icon.replace(/\_/g, '-')}`" cursor-pointer op70 text-base hover:op100 />
190213
</div>
191214
</div>
192215
</div>
193-
<div class="no-scrollbar flex-1 select-none overflow-scroll">
216+
<div v-if="tree.length" class="no-scrollbar flex-1 select-none overflow-scroll">
194217
<ComponentTree v-model="selected" :data="tree" />
195218
</div>
219+
<Empty v-else>
220+
No Data
221+
</Empty>
196222
</div>
197223
</Pane>
198224
<Pane size="60">
199225
<div class="h-full flex flex-col p2">
200-
<div v-if="nodeActions?.length" class="mb-1 flex justify-end pb-1" border="b dashed base">
201-
<div class="flex items-center gap-2 px-1">
226+
<div class="grid grid-cols-[1fr_auto] mb1 items-center gap2 pb1" border="b dashed base">
227+
<VueInput v-model="filterStateKey" :placeholder="inspectorState.stateFilterPlaceholder" />
228+
<div v-if="nodeActions?.length" class="flex items-center gap-2 px-1">
202229
<div v-for="(action, index) in nodeActions" :key="index" v-tooltip.bottom-end="{ content: action.tooltip }" class="flex items-center gap1" @click="callNodeAction(index)">
203230
<VueIcIcon :name="`baseline-${action.icon.replace(/\_/g, '-')}`" cursor-pointer op70 text-base hover:op100 />
204231
</div>
205232
</div>
206233
</div>
207-
<RootStateViewer v-if="selected && !emptyState" :data="state" :node-id="selected" :inspector-id="inspectorId" expanded-state-id="custom-inspector-state" class="no-scrollbar flex-1 select-none overflow-scroll" />
234+
<RootStateViewer v-if="selected && !emptyState" :data="displayState" :node-id="selected" :inspector-id="inspectorId" expanded-state-id="custom-inspector-state" class="no-scrollbar flex-1 select-none overflow-scroll" />
208235
<Empty v-else>
209236
No Data
210237
</Empty>
211238
</div>
212239
</Pane>
213240
</Splitpanes>
214241
</template>
215-
<Empty v-else>
216-
No Data
217-
</Empty>
218242
</div>
219243
</template>

packages/applet/src/modules/custom-inspector/index.vue

+2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ function getInspectorInfo() {
6868
logo: payload?.logo,
6969
timelineLayerIds: payload?.timelineLayers.map(item => item.id),
7070
pluginId: props.pluginId,
71+
treeFilterPlaceholder: payload.treeFilterPlaceholder,
72+
stateFilterPlaceholder: payload.stateFilterPlaceholder,
7173
}
7274
inspectorState.value = state
7375
restoreRouter()

packages/applet/src/modules/pinia/components/store/Index.vue

+30-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import type { CustomInspectorNode, CustomInspectorOptions, CustomInspectorState } from '@vue/devtools-kit'
33
import { DevToolsMessagingEvents, rpc } from '@vue/devtools-core'
44
import { parse } from '@vue/devtools-kit'
5-
import { vTooltip } from '@vue/devtools-ui'
5+
import { vTooltip, VueInput } from '@vue/devtools-ui'
66
import { Pane, Splitpanes } from 'splitpanes'
77
import { computed, onUnmounted, ref, watch } from 'vue'
88
import DevToolsHeader from '~/components/basic/DevToolsHeader.vue'
@@ -11,21 +11,40 @@ import Navbar from '~/components/basic/Navbar.vue'
1111
import RootStateViewer from '~/components/state/RootStateViewer.vue'
1212
import ComponentTree from '~/components/tree/TreeViewer.vue'
1313
14+
import { useCustomInspectorState } from '~/composables/custom-inspector-state'
1415
import { createExpandedContext } from '~/composables/toggle-expanded'
16+
import { filterInspectorState } from '~/utils'
17+
import { PiniaPluginInspectorId } from '../../constants'
1518
1619
const { expanded: expandedTreeNodes } = createExpandedContext()
1720
const { expanded: expandedStateNodes } = createExpandedContext('pinia-store-state')
1821
19-
const inspectorId = 'pinia'
22+
const inspectorId = PiniaPluginInspectorId
2023
const nodeActions = ref<CustomInspectorOptions['nodeActions']>([])
2124
const actions = ref<CustomInspectorOptions['nodeActions']>([])
25+
const inspectorState = useCustomInspectorState()
2226
2327
const selected = ref('')
2428
const tree = ref<CustomInspectorNode[]>([])
2529
const treeNodeLinkedList = computed(() => tree.value?.length ? dfs(tree.value?.[0]) : [])
2630
const flattenedTreeNodes = computed(() => flattenTreeNodes(tree.value))
2731
const flattenedTreeNodesIds = computed(() => flattenedTreeNodes.value.map(node => node.id))
2832
const state = ref<Record<string, CustomInspectorState[]>>({})
33+
const filterStoreKey = ref('')
34+
const filterStateKey = ref('')
35+
36+
watch(filterStoreKey, (value, oldValue) => {
37+
if (!value.trim().length && !oldValue.trim().length)
38+
return
39+
getPiniaInspectorTree(value)
40+
})
41+
42+
const displayState = computed(() => {
43+
return filterInspectorState({
44+
state: state.value,
45+
filterKey: filterStateKey.value,
46+
})
47+
})
2948
3049
const emptyState = computed(() => !state.value.state?.length && !state.value.getters?.length)
3150
@@ -118,8 +137,8 @@ watch(selected, () => {
118137
getPiniaState(selected.value)
119138
})
120139
121-
const getPiniaInspectorTree = () => {
122-
rpc.value.getInspectorTree({ inspectorId, filter: '' }).then((_data) => {
140+
function getPiniaInspectorTree(filter: string = '') {
141+
rpc.value.getInspectorTree({ inspectorId, filter }).then((_data) => {
123142
const data = parse(_data!)
124143
tree.value = data
125144
if (!selected.value && data.length) {
@@ -184,8 +203,9 @@ onUnmounted(() => {
184203
<Splitpanes class="flex-1 overflow-auto">
185204
<Pane border="r base" size="40" h-full>
186205
<div class="h-full flex flex-col p2">
187-
<div v-if="actions?.length" class="mb-1 flex justify-end pb-1" border="b dashed base">
188-
<div class="flex items-center gap-2 px-1">
206+
<div class="grid grid-cols-[1fr_auto] mb1 items-center gap2 pb1" border="b dashed base">
207+
<VueInput v-model="filterStoreKey" :placeholder="inspectorState.treeFilterPlaceholder" />
208+
<div v-if="actions?.length" class="flex items-center gap-2 px-1">
189209
<div v-for="(action, index) in actions" :key="index" v-tooltip.bottom-end="{ content: action.tooltip }" class="flex items-center gap1" @click="callAction(index)">
190210
<i :class="`i-ic-baseline-${action.icon.replace(/\_/g, '-')}`" cursor-pointer op70 text-base hover:op100 />
191211
</div>
@@ -198,14 +218,15 @@ onUnmounted(() => {
198218
</Pane>
199219
<Pane size="60">
200220
<div class="h-full flex flex-col p2">
201-
<div v-if="nodeActions?.length" class="mb-1 flex justify-end pb-1" border="b dashed base">
202-
<div class="flex items-center gap-2 px-1">
221+
<div class="grid grid-cols-[1fr_auto] mb1 items-center gap2 pb1" border="b dashed base">
222+
<VueInput v-model="filterStateKey" :placeholder="inspectorState.stateFilterPlaceholder" />
223+
<div v-if="nodeActions?.length" class="flex items-center gap-2 px-1">
203224
<div v-for="(action, index) in nodeActions" :key="index" v-tooltip.bottom-end="{ content: action.tooltip }" class="flex items-center gap1" @click="callNodeAction(index)">
204225
<i :class="`i-ic-baseline-${action.icon.replace(/\_/g, '-')}`" cursor-pointer op70 text-base hover:op100 />
205226
</div>
206227
</div>
207228
</div>
208-
<RootStateViewer v-if="selected && !emptyState" class="no-scrollbar flex-1 select-none overflow-scroll" :data="state" :node-id="selected" :inspector-id="inspectorId" expanded-state-id="pinia-store-state" />
229+
<RootStateViewer v-if="selected && !emptyState" class="no-scrollbar flex-1 select-none overflow-scroll" :data="displayState" :node-id="selected" :inspector-id="inspectorId" expanded-state-id="pinia-store-state" />
209230
<Empty v-else>
210231
No Data
211232
</Empty>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const PiniaPluginDescriptorId = 'dev.esm.pinia'
2+
export const PiniaPluginInspectorId = 'pinia'

packages/applet/src/modules/pinia/index.vue

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
<script setup lang="ts">
22
import { onRpcConnected, rpc } from '@vue/devtools-core'
33
import { computed, provide, ref } from 'vue'
4+
import { createCustomInspectorStateContext } from '~/composables/custom-inspector-state'
45
import { registerVirtualRouter, VirtualRoute } from '~/composables/virtual-router'
56
import About from './components/About.vue'
67
import Settings from './components/Settings.vue'
78
import Store from './components/store/Index.vue'
89
import Timeline from './components/timeline/Index.vue'
10+
import { PiniaPluginDescriptorId, PiniaPluginInspectorId } from './constants'
911
1012
const pluginSettings = ref(null)
1113
provide('pluginSettings', pluginSettings)
@@ -43,9 +45,10 @@ const { VirtualRouterView, restoreRouter } = registerVirtualRouter(routes, {
4345
defaultRoutePath: '/store',
4446
})
4547
48+
const inspectorState = createCustomInspectorStateContext()
49+
4650
onRpcConnected(() => {
47-
const pluginDescriptorId = 'dev.esm.pinia'
48-
rpc.value.getPluginSettings(pluginDescriptorId).then((settings) => {
51+
rpc.value.getPluginSettings(PiniaPluginDescriptorId).then((settings) => {
4952
if (settings.options) {
5053
// @ts-expect-error skip type check
5154
pluginSettings.value = settings
@@ -54,6 +57,14 @@ onRpcConnected(() => {
5457
pluginSettings.value = null
5558
}
5659
})
60+
rpc.value.getInspectorInfo(PiniaPluginInspectorId).then((payload) => {
61+
if (!payload)
62+
return
63+
inspectorState.value = {
64+
stateFilterPlaceholder: payload.stateFilterPlaceholder,
65+
treeFilterPlaceholder: payload.treeFilterPlaceholder,
66+
}
67+
})
5768
})
5869
</script>
5970

packages/applet/src/utils/search.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { INFINITY, isPlainObject, NAN, NEGATIVE_INFINITY, UNDEFINED } from '@vue/devtools-kit'
1+
import { CustomInspectorState, INFINITY, isPlainObject, NAN, NEGATIVE_INFINITY, UNDEFINED } from '@vue/devtools-kit'
22

33
/**
44
* Searches a key or value in the object, with a maximum deepness
@@ -132,3 +132,26 @@ function internalSearchArray(array, searchTerm, seen, depth) {
132132
}
133133
return match
134134
}
135+
136+
export function filterInspectorState<T extends CustomInspectorState>(params: {
137+
state: Record<string, T[]>
138+
filterKey?: string | null | undefined
139+
// Each group is a flatten object
140+
processGroup?: (item: T[]) => T[]
141+
}) {
142+
const { state, filterKey, processGroup } = params
143+
if (!filterKey || !filterKey.trim().length)
144+
return state
145+
const result = {}
146+
for (const groupKey in state) {
147+
const group = state[groupKey]
148+
const groupFields = group.filter(el => searchDeepInObject({
149+
// @ts-expect-error typing weak
150+
[el.key]: el.value,
151+
}, filterKey))
152+
if (groupFields.length) {
153+
result[groupKey] = processGroup ? processGroup(groupFields) : groupFields
154+
}
155+
}
156+
return result
157+
}

packages/devtools-kit/src/ctx/hook.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ export function createDevToolsCtxHooks() {
248248
const _payload = {
249249
app: plugin.descriptor.app,
250250
inspectorId,
251-
filter: inspector?.treeFilter || '',
251+
filter: inspector?.treeFilterPlaceholder || '',
252252
rootNodes: [],
253253
}
254254

packages/devtools-kit/src/ctx/inspector.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import { devtoolsTimelineLayers } from './timeline'
1111
interface DevToolsKitInspector {
1212
options: CustomInspectorOptions
1313
descriptor: PluginDescriptor
14-
treeFilter: string
14+
treeFilterPlaceholder: string
15+
stateFilterPlaceholder: string
1516
selectedNodeId: string
1617
appRecord: unknown
1718
}
@@ -31,7 +32,8 @@ export function addInspector(inspector: CustomInspectorOptions, descriptor: Plug
3132
devtoolsInspector.push({
3233
options: inspector,
3334
descriptor,
34-
treeFilter: '',
35+
treeFilterPlaceholder: inspector.treeFilterPlaceholder ?? 'Search tree...',
36+
stateFilterPlaceholder: inspector.stateFilterPlaceholder ?? 'Search state...',
3537
selectedNodeId: '',
3638
appRecord: getAppRecord(descriptor.app),
3739
})
@@ -76,6 +78,8 @@ export function getInspectorInfo(id: string) {
7678
packageName: descriptor.packageName,
7779
homepage: descriptor.homepage,
7880
timelineLayers,
81+
treeFilterPlaceholder: inspector.treeFilterPlaceholder,
82+
stateFilterPlaceholder: inspector.stateFilterPlaceholder,
7983
}
8084
}
8185

0 commit comments

Comments
 (0)