Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: filter tree/states for custom inspectors #664

Merged
merged 3 commits into from
Oct 31, 2024
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
2 changes: 2 additions & 0 deletions packages/applet/src/composables/custom-inspector-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ type CustomInspectorState = Partial<{
label: string
logo: string
timelineLayerIds: string[]
treeFilterPlaceholder: string
stateFilterPlaceholder: string
}>

const VueDevToolsStateSymbol: InjectionKey<Ref<CustomInspectorState>> = Symbol.for('VueDevToolsCustomInspectorStateSymbol')
Expand Down
33 changes: 10 additions & 23 deletions packages/applet/src/modules/components/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import RootStateViewer from '~/components/state/RootStateViewer.vue'
import ComponentTree from '~/components/tree/TreeViewer.vue'
import { createSelectedContext } from '~/composables/select'
import { createExpandedContext } from '~/composables/toggle-expanded'
import { searchDeepInObject } from '~/utils'
import { filterInspectorState } from '~/utils'
import ComponentRenderCode from './components/RenderCode.vue'

const emit = defineEmits(['openInEditor', 'onInspectComponentStart', 'onInspectComponentEnd'])
Expand Down Expand Up @@ -104,27 +104,14 @@ const activeTreeNode = computed(() => {
})
const activeTreeNodeFilePath = computed(() => activeTreeNode.value?.file ?? '')

const filteredState = computed(() => {
const result = {}
for (const groupKey in activeComponentState.value) {
const group = activeComponentState.value[groupKey]
const groupFields = group.filter((el) => {
try {
return searchDeepInObject({
[el.key]: el.value,
}, filterStateName.value)
}
catch (e) {
return {
[el.key]: e,
}
}
})
const normalized = flatten(Object.values(groupBy(sortByKey(groupFields), 'stateType')))
if (groupFields.length)
result[groupKey] = normalized
}
return result
const displayState = computed(() => {
return filterInspectorState({
state: activeComponentState.value,
filterKey: filterStateName.value,
processGroup(groupFields) {
return flatten(Object.values(groupBy(sortByKey(groupFields), 'stateType')))
},
})
})

const { expanded: expandedTreeNodes } = createExpandedContext()
Expand Down Expand Up @@ -357,7 +344,7 @@ function toggleApp(id: string) {
<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" />
</div>
</div>
<RootStateViewer class="no-scrollbar flex-1 select-none overflow-scroll" :data="filteredState" :node-id="activeComponentId" :inspector-id="inspectorId" expanded-state-id="component-state" />
<RootStateViewer class="no-scrollbar flex-1 select-none overflow-scroll" :data="displayState" :node-id="activeComponentId" :inspector-id="inspectorId" expanded-state-id="component-state" />
</div>
<ComponentRenderCode v-if="componentRenderCodeVisible && componentRenderCode" :code="componentRenderCode" @close="closeComponentRenderCode" />
</Pane>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { CustomInspectorNode, CustomInspectorOptions, CustomInspectorState } from '@vue/devtools-kit'
import { DevToolsMessagingEvents, onRpcConnected, rpc } from '@vue/devtools-core'
import { parse } from '@vue/devtools-kit'
import { vTooltip, VueIcIcon } from '@vue/devtools-ui'
import { vTooltip, VueIcIcon, VueInput } from '@vue/devtools-ui'
import { until } from '@vueuse/core'
import { Pane, Splitpanes } from 'splitpanes'
import { computed, onUnmounted, ref, watch } from 'vue'
Expand All @@ -13,6 +13,7 @@ import RootStateViewer from '~/components/state/RootStateViewer.vue'
import ComponentTree from '~/components/tree/TreeViewer.vue'
import { useCustomInspectorState } from '~/composables/custom-inspector-state'
import { createExpandedContext } from '~/composables/toggle-expanded'
import { filterInspectorState } from '~/utils'

const { expanded: expandedTreeNodes } = createExpandedContext()
const { expanded: expandedStateNodes } = createExpandedContext('custom-inspector-state')
Expand All @@ -32,6 +33,24 @@ const selected = ref('')
const state = ref<Record<string, CustomInspectorState[]>>({})
const emptyState = computed(() => !Object.keys(state.value).length)

const inspectorState = useCustomInspectorState()

const filterTreeKey = ref('')
const filterStateKey = ref('')

watch(filterTreeKey, (value, oldValue) => {
if (!value.trim().length && !oldValue.trim().length)
return
getInspectorTree(value)
})

const displayState = computed(() => {
return filterInspectorState({
state: state.value,
filterKey: filterStateKey.value,
})
})

// tree
function dfs(node: { id: string, children?: { id: string }[] }, path: string[] = [], linkedList: string[][] = []) {
path.push(node.id)
Expand Down Expand Up @@ -120,8 +139,8 @@ watch(selected, () => {
getInspectorState(selected.value)
})

const getInspectorTree = () => {
rpc.value.getInspectorTree({ inspectorId: inspectorId.value, filter: '' }).then((_data) => {
function getInspectorTree(filter = '') {
rpc.value.getInspectorTree({ inspectorId: inspectorId.value, filter }).then((_data) => {
const data = parse(_data!)
tree.value = data
if (!selected.value && data.length) {
Expand All @@ -132,7 +151,7 @@ const getInspectorTree = () => {
})
}

until(inspectorId).toBeTruthy().then(getInspectorTree)
until(inspectorId).toBeTruthy().then(() => getInspectorTree())

function onInspectorTreeUpdated(_data: string) {
const data = parse(_data) as {
Expand Down Expand Up @@ -179,41 +198,46 @@ onUnmounted(() => {
<DevToolsHeader :doc-link="customInspectState.homepage!">
<Navbar />
</DevToolsHeader>
<template v-if="tree.length">
<Empty v-if="!tree.length && !filterTreeKey.trim().length">
No Data
</Empty>
<template v-else>
<Splitpanes class="flex-1 overflow-auto">
<Pane border="r base" size="40" h-full>
<div class="h-full flex flex-col p2">
<div v-if="actions?.length" class="mb-1 flex justify-end pb-1" border="b dashed base">
<div class="flex items-center gap-2 px-1">
<div class="grid grid-cols-[1fr_auto] mb1 items-center gap2 pb1" border="b dashed base">
<VueInput v-model="filterTreeKey" :placeholder="inspectorState.treeFilterPlaceholder" />
<div v-if="actions?.length" class="flex items-center gap-2 px-1">
<div v-for="(action, index) in actions" :key="index" v-tooltip.bottom-end="{ content: action.tooltip }" class="flex items-center gap1" @click="callAction(index)">
<VueIcIcon :name="`baseline-${action.icon.replace(/\_/g, '-')}`" cursor-pointer op70 text-base hover:op100 />
</div>
</div>
</div>
<div class="no-scrollbar flex-1 select-none overflow-scroll">
<div v-if="tree.length" class="no-scrollbar flex-1 select-none overflow-scroll">
<ComponentTree v-model="selected" :data="tree" />
</div>
<Empty v-else>
No Data
</Empty>
</div>
</Pane>
<Pane size="60">
<div class="h-full flex flex-col p2">
<div v-if="nodeActions?.length" class="mb-1 flex justify-end pb-1" border="b dashed base">
<div class="flex items-center gap-2 px-1">
<div class="grid grid-cols-[1fr_auto] mb1 items-center gap2 pb1" border="b dashed base">
<VueInput v-model="filterStateKey" :placeholder="inspectorState.stateFilterPlaceholder" />
<div v-if="nodeActions?.length" class="flex items-center gap-2 px-1">
<div v-for="(action, index) in nodeActions" :key="index" v-tooltip.bottom-end="{ content: action.tooltip }" class="flex items-center gap1" @click="callNodeAction(index)">
<VueIcIcon :name="`baseline-${action.icon.replace(/\_/g, '-')}`" cursor-pointer op70 text-base hover:op100 />
</div>
</div>
</div>
<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" />
<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" />
<Empty v-else>
No Data
</Empty>
</div>
</Pane>
</Splitpanes>
</template>
<Empty v-else>
No Data
</Empty>
</div>
</template>
2 changes: 2 additions & 0 deletions packages/applet/src/modules/custom-inspector/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ function getInspectorInfo() {
logo: payload?.logo,
timelineLayerIds: payload?.timelineLayers.map(item => item.id),
pluginId: props.pluginId,
treeFilterPlaceholder: payload.treeFilterPlaceholder,
stateFilterPlaceholder: payload.stateFilterPlaceholder,
}
inspectorState.value = state
restoreRouter()
Expand Down
39 changes: 30 additions & 9 deletions packages/applet/src/modules/pinia/components/store/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { CustomInspectorNode, CustomInspectorOptions, CustomInspectorState } from '@vue/devtools-kit'
import { DevToolsMessagingEvents, rpc } from '@vue/devtools-core'
import { parse } from '@vue/devtools-kit'
import { vTooltip } from '@vue/devtools-ui'
import { vTooltip, VueInput } from '@vue/devtools-ui'
import { Pane, Splitpanes } from 'splitpanes'
import { computed, onUnmounted, ref, watch } from 'vue'
import DevToolsHeader from '~/components/basic/DevToolsHeader.vue'
Expand All @@ -11,21 +11,40 @@ import Navbar from '~/components/basic/Navbar.vue'
import RootStateViewer from '~/components/state/RootStateViewer.vue'
import ComponentTree from '~/components/tree/TreeViewer.vue'

import { useCustomInspectorState } from '~/composables/custom-inspector-state'
import { createExpandedContext } from '~/composables/toggle-expanded'
import { filterInspectorState } from '~/utils'
import { PiniaPluginInspectorId } from '../../constants'

const { expanded: expandedTreeNodes } = createExpandedContext()
const { expanded: expandedStateNodes } = createExpandedContext('pinia-store-state')

const inspectorId = 'pinia'
const inspectorId = PiniaPluginInspectorId
const nodeActions = ref<CustomInspectorOptions['nodeActions']>([])
const actions = ref<CustomInspectorOptions['nodeActions']>([])
const inspectorState = useCustomInspectorState()

const selected = ref('')
const tree = ref<CustomInspectorNode[]>([])
const treeNodeLinkedList = computed(() => tree.value?.length ? dfs(tree.value?.[0]) : [])
const flattenedTreeNodes = computed(() => flattenTreeNodes(tree.value))
const flattenedTreeNodesIds = computed(() => flattenedTreeNodes.value.map(node => node.id))
const state = ref<Record<string, CustomInspectorState[]>>({})
const filterStoreKey = ref('')
const filterStateKey = ref('')

watch(filterStoreKey, (value, oldValue) => {
if (!value.trim().length && !oldValue.trim().length)
return
getPiniaInspectorTree(value)
})

const displayState = computed(() => {
return filterInspectorState({
state: state.value,
filterKey: filterStateKey.value,
})
})

const emptyState = computed(() => !state.value.state?.length && !state.value.getters?.length)

Expand Down Expand Up @@ -118,8 +137,8 @@ watch(selected, () => {
getPiniaState(selected.value)
})

const getPiniaInspectorTree = () => {
rpc.value.getInspectorTree({ inspectorId, filter: '' }).then((_data) => {
function getPiniaInspectorTree(filter: string = '') {
rpc.value.getInspectorTree({ inspectorId, filter }).then((_data) => {
const data = parse(_data!)
tree.value = data
if (!selected.value && data.length) {
Expand Down Expand Up @@ -184,8 +203,9 @@ onUnmounted(() => {
<Splitpanes class="flex-1 overflow-auto">
<Pane border="r base" size="40" h-full>
<div class="h-full flex flex-col p2">
<div v-if="actions?.length" class="mb-1 flex justify-end pb-1" border="b dashed base">
<div class="flex items-center gap-2 px-1">
<div class="grid grid-cols-[1fr_auto] mb1 items-center gap2 pb1" border="b dashed base">
<VueInput v-model="filterStoreKey" :placeholder="inspectorState.treeFilterPlaceholder" />
<div v-if="actions?.length" class="flex items-center gap-2 px-1">
<div v-for="(action, index) in actions" :key="index" v-tooltip.bottom-end="{ content: action.tooltip }" class="flex items-center gap1" @click="callAction(index)">
<i :class="`i-ic-baseline-${action.icon.replace(/\_/g, '-')}`" cursor-pointer op70 text-base hover:op100 />
</div>
Expand All @@ -198,14 +218,15 @@ onUnmounted(() => {
</Pane>
<Pane size="60">
<div class="h-full flex flex-col p2">
<div v-if="nodeActions?.length" class="mb-1 flex justify-end pb-1" border="b dashed base">
<div class="flex items-center gap-2 px-1">
<div class="grid grid-cols-[1fr_auto] mb1 items-center gap2 pb1" border="b dashed base">
<VueInput v-model="filterStateKey" :placeholder="inspectorState.stateFilterPlaceholder" />
<div v-if="nodeActions?.length" class="flex items-center gap-2 px-1">
<div v-for="(action, index) in nodeActions" :key="index" v-tooltip.bottom-end="{ content: action.tooltip }" class="flex items-center gap1" @click="callNodeAction(index)">
<i :class="`i-ic-baseline-${action.icon.replace(/\_/g, '-')}`" cursor-pointer op70 text-base hover:op100 />
</div>
</div>
</div>
<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" />
<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" />
<Empty v-else>
No Data
</Empty>
Expand Down
2 changes: 2 additions & 0 deletions packages/applet/src/modules/pinia/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const PiniaPluginDescriptorId = 'dev.esm.pinia'
export const PiniaPluginInspectorId = 'pinia'
15 changes: 13 additions & 2 deletions packages/applet/src/modules/pinia/index.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<script setup lang="ts">
import { onRpcConnected, rpc } from '@vue/devtools-core'
import { computed, provide, ref } from 'vue'
import { createCustomInspectorStateContext } from '~/composables/custom-inspector-state'
import { registerVirtualRouter, VirtualRoute } from '~/composables/virtual-router'
import About from './components/About.vue'
import Settings from './components/Settings.vue'
import Store from './components/store/Index.vue'
import Timeline from './components/timeline/Index.vue'
import { PiniaPluginDescriptorId, PiniaPluginInspectorId } from './constants'

const pluginSettings = ref(null)
provide('pluginSettings', pluginSettings)
Expand Down Expand Up @@ -43,9 +45,10 @@ const { VirtualRouterView, restoreRouter } = registerVirtualRouter(routes, {
defaultRoutePath: '/store',
})

const inspectorState = createCustomInspectorStateContext()

onRpcConnected(() => {
const pluginDescriptorId = 'dev.esm.pinia'
rpc.value.getPluginSettings(pluginDescriptorId).then((settings) => {
rpc.value.getPluginSettings(PiniaPluginDescriptorId).then((settings) => {
if (settings.options) {
// @ts-expect-error skip type check
pluginSettings.value = settings
Expand All @@ -54,6 +57,14 @@ onRpcConnected(() => {
pluginSettings.value = null
}
})
rpc.value.getInspectorInfo(PiniaPluginInspectorId).then((payload) => {
if (!payload)
return
inspectorState.value = {
stateFilterPlaceholder: payload.stateFilterPlaceholder,
treeFilterPlaceholder: payload.treeFilterPlaceholder,
}
})
})
</script>

Expand Down
25 changes: 24 additions & 1 deletion packages/applet/src/utils/search.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { INFINITY, isPlainObject, NAN, NEGATIVE_INFINITY, UNDEFINED } from '@vue/devtools-kit'
import { CustomInspectorState, INFINITY, isPlainObject, NAN, NEGATIVE_INFINITY, UNDEFINED } from '@vue/devtools-kit'

/**
* Searches a key or value in the object, with a maximum deepness
Expand Down Expand Up @@ -132,3 +132,26 @@ function internalSearchArray(array, searchTerm, seen, depth) {
}
return match
}

export function filterInspectorState<T extends CustomInspectorState>(params: {
state: Record<string, T[]>
filterKey?: string | null | undefined
// Each group is a flatten object
processGroup?: (item: T[]) => T[]
}) {
const { state, filterKey, processGroup } = params
if (!filterKey || !filterKey.trim().length)
return state
const result = {}
for (const groupKey in state) {
const group = state[groupKey]
const groupFields = group.filter(el => searchDeepInObject({
// @ts-expect-error typing weak
[el.key]: el.value,
}, filterKey))
if (groupFields.length) {
result[groupKey] = processGroup ? processGroup(groupFields) : groupFields
}
}
return result
}
2 changes: 1 addition & 1 deletion packages/devtools-kit/src/ctx/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export function createDevToolsCtxHooks() {
const _payload = {
app: plugin.descriptor.app,
inspectorId,
filter: inspector?.treeFilter || '',
filter: inspector?.treeFilterPlaceholder || '',
rootNodes: [],
}

Expand Down
8 changes: 6 additions & 2 deletions packages/devtools-kit/src/ctx/inspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { devtoolsTimelineLayers } from './timeline'
interface DevToolsKitInspector {
options: CustomInspectorOptions
descriptor: PluginDescriptor
treeFilter: string
treeFilterPlaceholder: string
stateFilterPlaceholder: string
selectedNodeId: string
appRecord: unknown
}
Expand All @@ -31,7 +32,8 @@ export function addInspector(inspector: CustomInspectorOptions, descriptor: Plug
devtoolsInspector.push({
options: inspector,
descriptor,
treeFilter: '',
treeFilterPlaceholder: inspector.treeFilterPlaceholder ?? 'Search tree...',
stateFilterPlaceholder: inspector.stateFilterPlaceholder ?? 'Search state...',
selectedNodeId: '',
appRecord: getAppRecord(descriptor.app),
})
Expand Down Expand Up @@ -76,6 +78,8 @@ export function getInspectorInfo(id: string) {
packageName: descriptor.packageName,
homepage: descriptor.homepage,
timelineLayers,
treeFilterPlaceholder: inspector.treeFilterPlaceholder,
stateFilterPlaceholder: inspector.stateFilterPlaceholder,
}
}

Expand Down