Skip to content

Right click Inspect component + this.$inspect() #479

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

Merged
merged 17 commits into from
Jan 12, 2018
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 2 additions & 1 deletion shells/chrome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"permissions": [
"http://*/*",
"https://*/*",
"file:///*"
"file:///*",
"contextMenus"
],

"content_scripts": [
Expand Down
60 changes: 59 additions & 1 deletion shells/chrome/src/devtools-background.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// Vue presence on the page. If yes, create the Vue panel; otherwise poll
// for 10 seconds.

let panelShown = false
let pendingAction
let created = false
let checkCount = 0

Expand All @@ -14,6 +16,7 @@ function createPanelIfHasVue () {
if (created || checkCount++ > 10) {
return
}
panelShown = false
chrome.devtools.inspectedWindow.eval(
'!!(window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue)',
function (hasVue) {
Expand All @@ -24,10 +27,65 @@ function createPanelIfHasVue () {
created = true
chrome.devtools.panels.create(
'Vue', 'icons/128.png', 'devtools.html',
function (panel) {
panel => {
// panel loaded
panel.onShown.addListener(onPanelShown)
panel.onHidden.addListener(onPanelHidden)
}
)
}
)
}

// Manage panel visibility

function onPanelShown () {
chrome.runtime.sendMessage('vue-panel-shown')
panelShown = true
}

function onPanelHidden () {
chrome.runtime.sendMessage('vue-panel-hidden')
panelShown = false
}

// Page context menu entry

chrome.contextMenus.create({
id: 'vue-inspect-instance',
title: 'Inspect Vue component',
contexts: ['all']
})

chrome.contextMenus.onClicked.addListener(genericOnContext)

function genericOnContext (info, tab) {
if (info.menuItemId === 'vue-inspect-instance') {
panelAction(() => {
chrome.runtime.sendMessage('vue-get-context-menu-target')
})
}
}

// Action that may execute immediatly
// or later when the Vue panel is ready

function panelAction (cb) {
if (created && panelShown) {
cb()
} else {
pendingAction = cb
}
}

// Execute pending action when Vue panel is ready

chrome.runtime.onMessage.addListener(request => {
if (request === 'vue-panel-load') {
onPanelLoad()
}
})

function onPanelLoad () {
pendingAction && pendingAction()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when does the pendingAction gets executed? I couldn't test this one
Depending on when it is triggered, it may be better to set pendingAction back to null

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

}
6 changes: 6 additions & 0 deletions src/backend/hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,10 @@ export function installHook (window) {
return hook
}
})

// Start recording context menu when Vue is detected
// event if Vue devtools are not loaded yet
document.addEventListener('contextmenu', event => {
window.__VUE_DEVTOOLS_CONTEXT_MENU_TARGET = event.target
})
}
17 changes: 16 additions & 1 deletion src/backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ function connect () {
const instance = instanceMap.get(id)
if (instance) {
scrollIntoView(instance)
highlight(instance)
}
bindToConsole(instance)
flush()
Expand All @@ -80,6 +79,22 @@ function connect () {
bridge.on('enter-instance', id => highlight(instanceMap.get(id)))
bridge.on('leave-instance', unHighlight)

// Get the instance id that is targeted by context menu
bridge.on('get-context-menu-target', () => {
let lastContextMenuTarget = window.__VUE_DEVTOOLS_CONTEXT_MENU_TARGET
if (lastContextMenuTarget) {
// Search for parent that "is" a component instance
while (!lastContextMenuTarget.__vue__ && lastContextMenuTarget.parentElement) {
lastContextMenuTarget = lastContextMenuTarget.parentElement
}
const instance = lastContextMenuTarget.__vue__
if (instance) {
const id = instance.__VUE_DEVTOOLS_UID__
id && bridge.send('context-menu-target', id)
}
}
})

// vuex
if (hook.store) {
initVuexBackend(hook, bridge)
Expand Down
3 changes: 3 additions & 0 deletions src/devtools/global.styl
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,6 @@ $arrow-color = #444
text-align center
padding 0.5em
margin 0 auto

.scroll
position relative
60 changes: 60 additions & 0 deletions src/devtools/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import App from './App.vue'
import store from './store'
import { parse } from '../util'

let panelShown = false
let pendingAction = null

// Capture and log devtool errors when running as actual extension
// so that we can debug it by inspecting the background page.
// We do want the errors to be thrown in the dev shell though.
Expand All @@ -14,6 +17,16 @@ if (typeof chrome !== 'undefined' && chrome.devtools) {
component: vm.$options.name || vm.$options._componentTag || 'anonymous'
})
}

chrome.runtime.onMessage.addListener(request => {
if (request === 'vue-panel-shown') {
onPanelShown()
} else if (request === 'vue-panel-hidden') {
onPanelHidden()
} else if (request === 'vue-get-context-menu-target') {
getContextMenuInstance()
}
})
}

Vue.options.renderError = (h, e) => {
Expand Down Expand Up @@ -67,6 +80,10 @@ function initApp (shell) {
)
bridge.send('vuex:toggle-recording', store.state.vuex.enabled)
bridge.send('events:toggle-recording', store.state.events.enabled)

if (typeof chrome !== 'undefined' && chrome.devtools) {
chrome.runtime.sendMessage('vue-panel-load')
}
})

bridge.once('proxy-fail', () => {
Expand Down Expand Up @@ -99,6 +116,12 @@ function initApp (shell) {
}
})

bridge.on('context-menu-target', id => {
ensurePaneShown(() => {
inspectInstance(id)
})
})

app = new Vue({
store,
render (h) {
Expand All @@ -107,3 +130,40 @@ function initApp (shell) {
}).$mount('#app')
})
}

function getContextMenuInstance () {
bridge.send('get-context-menu-target')
}

function inspectInstance (id) {
bridge.send('select-instance', id)
store.commit('SWITCH_TAB', 'components')
const instance = store.state.components.instancesMap[id]
instance && store.dispatch('components/toggleInstance', {
instance,
expanded: true,
parent: true
})
}

// Pane visibility management

function ensurePaneShown (cb) {
if (panelShown) {
cb()
} else {
pendingAction = cb
}
}

function onPanelShown () {
panelShown = true
if (pendingAction) {
pendingAction()
pendingAction = null
}
}

function onPanelHidden () {
panelShown = false
}
21 changes: 20 additions & 1 deletion src/devtools/views/components/ComponentInstance.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
inactive: instance.inactive,
selected: selected
}">
<div class="self"
<div
ref="self"
class="self"
@click.stop="select"
@dblclick.stop="toggle"
@mouseenter="enter"
Expand Down Expand Up @@ -46,6 +48,8 @@
</template>

<script>
import { scrollIntoView } from '../../../util'

export default {
name: 'ComponentInstance',
props: {
Expand All @@ -59,6 +63,9 @@ export default {
}
},
computed: {
scrollToExpanded () {
return this.$store.state.components.scrollToExpanded
},
expanded () {
return !!this.$store.state.components.expansionMap[this.instance.id]
},
Expand All @@ -73,6 +80,18 @@ export default {
})
}
},
watch: {
scrollToExpanded: {
handler (value, oldValue) {
if (value !== oldValue && value === this.instance.id) {
this.$nextTick(() => {
scrollIntoView(document.querySelector('.left .scroll'), this.$refs.self)
})
}
},
immediate: true
}
},
methods: {
toggle (event) {
this.toggleWithValue(!this.expanded, event.altKey)
Expand Down
51 changes: 46 additions & 5 deletions src/devtools/views/components/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ const state = {
selected: null,
inspectedInstance: {},
instances: [],
instancesMap: {},
expansionMap: {},
events: []
events: [],
scrollToExpanded: null
}

const mutations = {
Expand All @@ -14,8 +16,26 @@ const mutations = {
if (process.env.NODE_ENV !== 'production') {
start = window.performance.now()
}

// Instance ID map
// + add 'parent' properties
const map = {}
function walk (instance) {
map[instance.id] = instance
if (instance.children) {
instance.children.forEach(child => {
child.parent = instance
walk(child)
})
}
}
payload.instances.forEach(walk)

// Mutations
state.instances = Object.freeze(payload.instances)
state.inspectedInstance = Object.freeze(payload.inspectedInstance)
state.instancesMap = Object.freeze(map)

if (process.env.NODE_ENV !== 'production') {
Vue.nextTick(() => {
console.log(`devtools render took ${window.performance.now() - start}ms.`)
Expand All @@ -24,15 +44,23 @@ const mutations = {
},
RECEIVE_INSTANCE_DETAILS (state, instance) {
state.inspectedInstance = Object.freeze(instance)
state.scrollToExpanded = null
},
TOGGLE_INSTANCE ({ expansionMap }, { id, expanded }) {
Vue.set(expansionMap, id, expanded)
TOGGLE_INSTANCE (state, { id, expanded, scrollTo = null } = {}) {
Vue.set(state.expansionMap, id, expanded)
state.scrollToExpanded = scrollTo
}
}

const actions = {
toggleInstance ({ commit, dispatch }, { instance, expanded, recursive }) {
commit('TOGGLE_INSTANCE', { id: instance.id, expanded })
toggleInstance ({ commit, dispatch, state }, { instance, expanded, recursive, parent = false } = {}) {
const id = instance.id

commit('TOGGLE_INSTANCE', {
id,
expanded,
scrollTo: parent ? id : null
})

if (recursive) {
instance.children.forEach((child) => {
Expand All @@ -43,6 +71,19 @@ const actions = {
})
})
}

// Expand the parents
if (parent) {
let i = instance
while (i.parent) {
i = i.parent
commit('TOGGLE_INSTANCE', {
id: i.id,
expanded: true,
scrollTo: id
})
}
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,9 @@ export function sortByKey (state) {
return 0
})
}

export function scrollIntoView (scrollParent, el) {
const top = el.offsetTop
const height = el.offsetHeight
scrollParent.scrollTop = top + (height - scrollParent.offsetHeight) / 2
}