|
import { BridgeEvents, SharedData, createThrottleQueue, parse, stringify } from '@vue-devtools/shared-utils' |
|
import type { AppRecord, BackendContext } from '@vue-devtools/app-backend-api' |
|
import { BuiltinBackendFeature } from '@vue-devtools/app-backend-api' |
|
import type { App, ComponentInstance, EditStatePayload } from '@vue/devtools-api' |
|
import { getAppRecord } from './app' |
|
|
|
const MAX_$VM = 10 |
|
const $vmQueue = [] |
|
|
|
export async function sendComponentTreeData(appRecord: AppRecord, instanceId: string, filter = '', maxDepth: number = null, recursively = false, ctx: BackendContext) { |
|
if (!instanceId || appRecord !== ctx.currentAppRecord) { |
|
return |
|
} |
|
|
|
|
|
|
|
if ( |
|
instanceId !== '_root' |
|
&& ctx.currentAppRecord.backend.options.features.includes(BuiltinBackendFeature.FLUSH) |
|
) { |
|
return |
|
} |
|
|
|
const instance = getComponentInstance(appRecord, instanceId, ctx) |
|
if (!instance) { |
|
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_TREE, { |
|
instanceId, |
|
treeData: null, |
|
notFound: true, |
|
}) |
|
} |
|
else { |
|
if (filter) { |
|
filter = filter.toLowerCase() |
|
} |
|
if (maxDepth == null) { |
|
maxDepth = instance === ctx.currentAppRecord.rootInstance ? 2 : 1 |
|
} |
|
const data = await appRecord.backend.api.walkComponentTree(instance, maxDepth, filter, recursively) |
|
const payload = { |
|
instanceId, |
|
treeData: stringify(data), |
|
} |
|
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_TREE, payload) |
|
} |
|
} |
|
|
|
export async function sendSelectedComponentData(appRecord: AppRecord, instanceId: string, ctx: BackendContext) { |
|
if (!instanceId || appRecord !== ctx.currentAppRecord) { |
|
return |
|
} |
|
const instance = getComponentInstance(appRecord, instanceId, ctx) |
|
if (!instance) { |
|
sendEmptyComponentData(instanceId, ctx) |
|
} |
|
else { |
|
|
|
if (typeof window !== 'undefined') { |
|
const win = window as any |
|
win.$vm = instance |
|
|
|
|
|
if ($vmQueue[0] !== instance) { |
|
if ($vmQueue.length >= MAX_$VM) { |
|
$vmQueue.pop() |
|
} |
|
for (let i = $vmQueue.length; i > 0; i--) { |
|
win[`$vm${i}`] = $vmQueue[i] = $vmQueue[i - 1] |
|
} |
|
win.$vm0 = $vmQueue[0] = instance |
|
} |
|
} |
|
if (SharedData.debugInfo) { |
|
|
|
console.log('[DEBUG] inspect', instance) |
|
} |
|
const parentInstances = await appRecord.backend.api.walkComponentParents(instance) |
|
const payload = { |
|
instanceId, |
|
data: stringify(await appRecord.backend.api.inspectComponent(instance, ctx.currentAppRecord.options.app)), |
|
parentIds: parentInstances.map(i => i.__VUE_DEVTOOLS_UID__), |
|
} |
|
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_SELECTED_DATA, payload) |
|
markSelectedInstance(instanceId, ctx) |
|
} |
|
} |
|
|
|
export function markSelectedInstance(instanceId: string, ctx: BackendContext) { |
|
ctx.currentInspectedComponentId = instanceId |
|
ctx.currentAppRecord.lastInspectedComponentId = instanceId |
|
} |
|
|
|
export function sendEmptyComponentData(instanceId: string, ctx: BackendContext) { |
|
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_SELECTED_DATA, { |
|
instanceId, |
|
data: null, |
|
}) |
|
} |
|
|
|
export async function editComponentState(instanceId: string, dotPath: string, type: string, state: EditStatePayload, ctx: BackendContext) { |
|
if (!instanceId) { |
|
return |
|
} |
|
const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx) |
|
if (instance) { |
|
if ('value' in state && state.value != null) { |
|
state.value = parse(state.value, true) |
|
} |
|
await ctx.currentAppRecord.backend.api.editComponentState(instance, dotPath, type, state, ctx.currentAppRecord.options.app) |
|
await sendSelectedComponentData(ctx.currentAppRecord, instanceId, ctx) |
|
} |
|
} |
|
|
|
export async function getComponentId(app: App, uid: number, instance: ComponentInstance, ctx: BackendContext) { |
|
try { |
|
if (instance.__VUE_DEVTOOLS_UID__) { |
|
return instance.__VUE_DEVTOOLS_UID__ |
|
} |
|
const appRecord = await getAppRecord(app, ctx) |
|
if (!appRecord) { |
|
return null |
|
} |
|
const isRoot = appRecord.rootInstance === instance |
|
return `${appRecord.id}:${isRoot ? 'root' : uid}` |
|
} |
|
catch (e) { |
|
if (SharedData.debugInfo) { |
|
console.error(e) |
|
} |
|
return null |
|
} |
|
} |
|
|
|
export function getComponentInstance(appRecord: AppRecord, instanceId: string, _ctx: BackendContext) { |
|
if (instanceId === '_root') { |
|
instanceId = `${appRecord.id}:root` |
|
} |
|
const instance = appRecord.instanceMap.get(instanceId) |
|
if (!instance) { |
|
appRecord.missingInstanceQueue.add(instanceId) |
|
if (SharedData.debugInfo) { |
|
console.warn(`Instance uid=${instanceId} not found`) |
|
} |
|
} |
|
return instance |
|
} |
|
|
|
export async function refreshComponentTreeSearch(ctx: BackendContext) { |
|
if (!ctx.currentAppRecord.componentFilter) { |
|
return |
|
} |
|
await sendComponentTreeData(ctx.currentAppRecord, '_root', ctx.currentAppRecord.componentFilter, null, false, ctx) |
|
} |
|
|
|
const updateTrackingQueue = createThrottleQueue(500) |
|
|
|
export function sendComponentUpdateTracking(instanceId: string, time: number, ctx: BackendContext) { |
|
if (!instanceId) { |
|
return |
|
} |
|
|
|
updateTrackingQueue.add(instanceId, () => { |
|
const payload = { |
|
instanceId, |
|
time, |
|
} |
|
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_UPDATED, payload) |
|
}) |
|
} |
|
|