Skip to content
Open
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: 1 addition & 1 deletion packages/applet/src/components/state/ChildStateViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import StateFieldViewer from './StateFieldViewer.vue'

withDefaults(defineProps<{
data: CustomInspectorState[]
depth: number
depth?: number
index: string
expandedStateId?: string
}>(), {
Expand Down
45 changes: 39 additions & 6 deletions packages/applet/src/components/tree/TreeViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { ComponentTreeNode, InspectorTree } from '@vue/devtools-kit'
import { UNDEFINED } from '@vue/devtools-kit'
import { vTooltip } from '@vue/devtools-ui'
import { ref } from 'vue'
import NodeTag from '~/components/basic/NodeTag.vue'
import ToggleExpanded from '~/components/basic/ToggleExpanded.vue'
import ComponentTreeViewer from '~/components/tree/TreeViewer.vue'
Expand All @@ -10,8 +11,8 @@ import { useToggleExpanded } from '~/composables/toggle-expanded'

const props = withDefaults(defineProps<{
data: ComponentTreeNode[] | InspectorTree[]
depth: number
withTag: boolean
depth?: number
withTag?: boolean
}>(), {
depth: 0,
withTag: false,
Expand All @@ -20,6 +21,7 @@ const emit = defineEmits(['hover', 'leave'])
const selectedNodeId = defineModel()
const { expanded, toggleExpanded } = useToggleExpanded()
const { select: _select } = useSelect()
const hoveredNodeId = ref<string | null>(null)

function normalizeLabel(item: ComponentTreeNode | InspectorTree) {
return ('name' in item && item?.name) || ('label' in item && item.label)
Expand All @@ -28,6 +30,20 @@ function normalizeLabel(item: ComponentTreeNode | InspectorTree) {
function select(id: string) {
selectedNodeId.value = id
}
function onNodeHover(item: ComponentTreeNode | InspectorTree) {
hoveredNodeId.value = item.id
emit('hover', item.id)
}

function onNodeLeave() {
hoveredNodeId.value = null
emit('leave')
}

function onExpandToggleClick(event: Event, itemId: string) {
event.stopPropagation()
toggleExpanded(itemId)
}
</script>

<template>
Expand All @@ -39,19 +55,19 @@ function select(id: string) {
}"
>
<div
class="group flex cursor-pointer items-center rounded-1 hover:(bg-primary-300 dark:bg-gray-600)"
class="group relative flex cursor-pointer items-center rounded-1 hover:(bg-primary-300 dark:bg-gray-600)"
:style=" { paddingLeft: `${15 * depth + 4}px` }"
:class="{ 'bg-primary-600! active': selectedNodeId === item.id }"
@click="select(item.id)"
@dblclick="toggleExpanded(item.id)"
@mouseover="() => emit('hover', item.id)"
@mouseleave="() => emit('leave')"
@mouseover="onNodeHover(item)"
@mouseleave="onNodeLeave"
>
<ToggleExpanded
v-if="item?.children?.length"
:value="expanded.includes(item.id)"
class="[.active_&]:op20 group-hover:op20"
@click.stop="toggleExpanded(item.id)"
@click.stop="onExpandToggleClick($event, item.id)"
/>
<!-- placeholder -->
<span v-else pl5 />
Expand Down Expand Up @@ -85,6 +101,23 @@ function select(id: string) {
inactive
</span>
<NodeTag v-for="(_item, _index) in item.tags" :key="_index" :tag="_item" />

<!-- Hover actions for nodes with children -->
<div
v-if="item?.children?.length && hoveredNodeId === item.id"
class="absolute right-2 flex items-center gap-1 border border-gray-200 rounded bg-white px-1 shadow-sm dark:border-gray-600 dark:bg-gray-800"
>
<div
v-tooltip="expanded.includes(item.id) ? 'Collapse' : 'Expand'"
class="flex cursor-pointer items-center rounded p-0.5 hover:bg-gray-100 dark:hover:bg-gray-700"
@click.stop="onExpandToggleClick($event, item.id)"
>
<i
:class="expanded.includes(item.id) ? 'i-ic-baseline-unfold-less' : 'i-ic-baseline-unfold-more'"
class="text-xs opacity-70 hover:opacity-100"
/>
</div>
</div>
</div>
<div
v-if="item?.children?.length && expanded.includes(item.id)"
Expand Down
136 changes: 127 additions & 9 deletions packages/applet/src/modules/pinia/components/store/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ watch(filterStoreKey, (value, oldValue) => {
if (!value.trim().length && !oldValue.trim().length)
return
getPiniaInspectorTree(value)
// Auto-expand when searching
if (value.trim().length) {
setTimeout(() => {
expandAllTreeNodes()
if (selected.value) {
expandAllStateNodes()
}
}, 100)
}
})

const displayState = computed(() => {
Expand Down Expand Up @@ -72,6 +81,103 @@ function getNodesByDepth(list: string[][], depth: number) {
return [...new Set(nodes)]
}

function expandAllTreeNodes() {
// Recursively expand all tree nodes
const getAllNodeIds = (nodes: CustomInspectorNode[]): string[] => {
const ids: string[] = []
const traverse = (node: CustomInspectorNode) => {
if (node?.id) {
ids.push(node.id)
if (node.children?.length) {
node.children.forEach(traverse)
}
}
}
nodes.forEach(traverse)
return ids
}

expandedTreeNodes.value = getAllNodeIds(tree.value)
}

function collapseAllTreeNodes() {
expandedTreeNodes.value = []
}

function expandAllStateNodes() {
// Recursively expand all state nodes, including deepest nested levels
const getAllStateIds = (obj: any, prefix = '', depth = 0): string[] => {
const ids: string[] = []

// Add depth limit but allow deeper expansion
if (depth > 30 || !obj)
return ids

if (prefix)
ids.push(prefix)

try {
if (Array.isArray(obj)) {
obj.forEach((item, index) => {
const path = prefix ? `${prefix}.${index}` : `${index}`
ids.push(path)
// Continue recursively expanding array items
ids.push(...getAllStateIds(item, path, depth + 1))
})
}
else if (typeof obj === 'object' && obj !== null) {
// Skip built-in objects but expand plain objects
if (obj instanceof Date || obj instanceof RegExp || obj instanceof Error)
return ids

// Expand all object properties
Object.keys(obj).forEach((key) => {
const path = prefix ? `${prefix}.${key}` : key
ids.push(path)
ids.push(...getAllStateIds(obj[key], path, depth + 1))
})
}
}
catch (error) {
console.warn('Error during state expansion:', error)
}

return ids
}

const allIds: string[] = []

// Expand all state sections
Object.keys(state.value).forEach((section, sectionIndex) => {
allIds.push(`${sectionIndex}`)

const sectionData = state.value[section]
if (Array.isArray(sectionData)) {
sectionData.forEach((item: any, itemIndex: number) => {
const itemPath = `${sectionIndex}.${itemIndex}`
allIds.push(itemPath)

if (item && typeof item === 'object') {
// Extract actual value and deeply expand
const valueToExpand = item.value !== undefined ? item.value : item
allIds.push(...getAllStateIds(valueToExpand, itemPath))
}
})
}
else if (sectionData && typeof sectionData === 'object') {
// Directly expand object-type sections
allIds.push(...getAllStateIds(sectionData, `${sectionIndex}`))
}
})

// Ensure all paths are expanded
expandedStateNodes.value = [...new Set(allIds)]
}

function collapseAllStateNodes() {
expandedStateNodes.value = []
}

function flattenTreeNodes(tree: CustomInspectorNode[]) {
const res: CustomInspectorNode[] = []
const find = (treeNode: CustomInspectorNode[]) => {
Expand Down Expand Up @@ -124,7 +230,9 @@ function getPiniaState(nodeId: string) {
return
// @ts-expect-error skip type check
state.value = filterEmptyState(parsedData)
expandedStateNodes.value = Array.from({ length: Object.keys(state.value).length }, (_, i) => `${i}`)
// Auto-expand state sections and first level items
const stateIds = Object.keys(state.value).map((_, i) => `${i}`)
expandedStateNodes.value = stateIds
})
}

Expand All @@ -144,7 +252,8 @@ function getPiniaInspectorTree(filter: string = '') {
if (!selected.value && data.length) {
selected.value = data[0].id
getPiniaState(data[0].id)
expandedTreeNodes.value = getNodesByDepth(treeNodeLinkedList.value, 1)
// Auto-expand first 6 levels by default for better UX
expandedTreeNodes.value = getNodesByDepth(treeNodeLinkedList.value, 6)
}
})
}
Expand All @@ -161,7 +270,8 @@ function onInspectorTreeUpdated(_data: string) {

if (!flattenedTreeNodesIds.value.includes(selected.value)) {
selected.value = data.rootNodes[0].id
expandedTreeNodes.value = getNodesByDepth(treeNodeLinkedList.value, 1)
// Auto-expand first 6 levels by default for better UX
expandedTreeNodes.value = getNodesByDepth(treeNodeLinkedList.value, 6)
getPiniaState(data.rootNodes[0].id)
}
}
Expand Down Expand Up @@ -206,25 +316,33 @@ onUnmounted(() => {
<Pane border="r base" size="40" h-full>
<div class="h-full flex flex-col p2">
<div class="grid grid-cols-[1fr_auto] mb1 items-center gap2 pb1" border="b dashed base">
<VueInput v-model="filterStoreKey" :placeholder="inspectorState.treeFilterPlaceholder" />
<VueInput v-model="filterStoreKey" :placeholder="inspectorState.treeFilterPlaceholder || 'Search stores...'" clearable />
<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 text-base op70 hover:op100 />
</div>
</div>
</div>
<div class="no-scrollbar flex-1 select-none overflow-scroll">
<ComponentTree v-model="selected" :data="tree" />
<ComponentTree v-model="selected" :data="tree" :depth="0" :with-tag="false" />
</div>
</div>
</Pane>
<Pane size="60">
<div class="h-full flex flex-col p2">
<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 text-base op70 hover:op100 />
<VueInput v-model="filterStateKey" :placeholder="inspectorState.stateFilterPlaceholder || 'Search state properties...'" clearable />
<div class="flex items-center gap-2 px-1">
<div v-tooltip.bottom-end="{ content: 'Expand All' }" class="flex items-center gap1" @click="expandAllStateNodes">
<i class="i-ic-baseline-unfold-more" cursor-pointer text-base op70 hover:op100 />
</div>
<div v-tooltip.bottom-end="{ content: 'Collapse All' }" class="flex items-center gap1" @click="collapseAllStateNodes">
<i class="i-ic-baseline-unfold-less" cursor-pointer text-base op70 hover:op100 />
</div>
<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 text-base op70 hover:op100 />
</div>
</div>
</div>
</div>
Expand Down
Loading