Skip to content

Commit

Permalink
Select component from dom tree (vuejs#476)
Browse files Browse the repository at this point in the history
* Add select component example

* Extracted component selection to class and moved select icon

* Add test, fix codestyle

* Fix error when no component has been highlighted

* Use inspect-instance method for selecting instance

* Remove isDark

* Increase wide and tall vars (we are starting to have lots of buttons)

* Header improvements

* Highlighter improvements

* Selector improvements (blocks events + better component search)

* Updated testing components

* WIP fixing tests

* Icon pulse

* Button text changes

* Fix tests

* Unecessary style

* Rename searchComponent

* Fixes selecting not disabled if component selected in tree

* Keyboard shortcut
  • Loading branch information
nicoeg authored and Guillaume Chau committed Jan 14, 2018
1 parent 26de8f4 commit 1fe68b2
Show file tree
Hide file tree
Showing 16 changed files with 302 additions and 63 deletions.
7 changes: 6 additions & 1 deletion shells/dev/target/Other.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default {
},
components: {
mine: {
render: h => h('div', null, 'mine'),
render: h => h('div', { class: 'mine' }, 'mine'),
data () {
return {
// testing all data types
Expand All @@ -50,3 +50,8 @@ export default {
}
}
</script>

<style lang="stylus">
.mine
display inline-block
</style>
41 changes: 36 additions & 5 deletions shells/dev/target/Target.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@
<input @keyup.enter="regex = new RegExp($event.target.value)"/>
<span>(Press enter to set)</span>
<br/>
<button class="add" @click="add">Add</button>
<button class="remove" @click="rm">Remove</button>
<button class="add" @mouseup="add">Add</button>
<button class="remove" @mousedown="rm">Remove</button>
<input v-model="localMsg">
<other v-for="item in items" :key="item" :id="item"></other>
<button @click="inspect">Inspect component</button>
<div>
<button
class="inspect"
@click="inspect"
@mouseover="over = true"
@mouseout="over = false"
>Inspect component</button>
<span v-if="over" class="over">Mouse over</span>
</div>
</div>
</template>

Expand All @@ -30,7 +38,8 @@ export default {
regex: /(a\w+b)/g,
nan: NaN,
infinity: Infinity,
negativeInfinity: -Infinity
negativeInfinity: -Infinity,
over: false
}
},
computed: {
Expand All @@ -46,7 +55,12 @@ export default {
},
methods: {
add () {
this.items.push(1, 2, 3)
const l = this.items.length
this.items.push(
l + 1,
l + 2,
l + 3
)
},
rm () {
this.items.pop()
Expand All @@ -57,3 +71,20 @@ export default {
}
}
</script>

<style lang="stylus" scoped>
.inspect
border solid 1px black
background #eee
color black
border-radius 2px
padding 6px 12px
cursor pointer
&:hover
border-color blue
color blue
.over
pointer-events none
margin-left 12px
</style>
93 changes: 93 additions & 0 deletions src/backend/component-selector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { highlight, unHighlight } from './highlighter'
import { findRelatedComponent } from './utils'

export default class ComponentSelector {
constructor (bridge, instanceMap) {
const self = this
self.bridge = bridge
self.instanceMap = instanceMap
self.bindMethods()

bridge.on('start-component-selector', self.startSelecting)
bridge.on('stop-component-selector', self.stopSelecting)
}

/**
* Adds event listeners for mouseover and mouseup
*/
startSelecting () {
document.body.addEventListener('mouseover', this.elementMouseOver, true)
document.body.addEventListener('click', this.elementClicked, true)
document.body.addEventListener('mouseout', this.cancelEvent, true)
document.body.addEventListener('mouseenter', this.cancelEvent, true)
document.body.addEventListener('mouseleave', this.cancelEvent, true)
document.body.addEventListener('mousedown', this.cancelEvent, true)
document.body.addEventListener('mouseup', this.cancelEvent, true)
}

/**
* Removes event listeners
*/
stopSelecting () {
document.body.removeEventListener('mouseover', this.elementMouseOver, true)
document.body.removeEventListener('click', this.elementClicked, true)
document.body.removeEventListener('mouseout', this.cancelEvent, true)
document.body.removeEventListener('mouseenter', this.cancelEvent, true)
document.body.removeEventListener('mouseleave', this.cancelEvent, true)
document.body.removeEventListener('mousedown', this.cancelEvent, true)
document.body.removeEventListener('mouseup', this.cancelEvent, true)

unHighlight()
}

/**
* Highlights a component on element mouse over
* @param {MouseEvent} e
*/
elementMouseOver (e) {
this.cancelEvent(e)

const el = e.target
if (el) {
this.selectedInstance = findRelatedComponent(el)
}

unHighlight()
if (this.selectedInstance) {
highlight(this.selectedInstance)
}
}

/**
* Selects an instance in the component view
* @param {MouseEvent} e
*/
elementClicked (e) {
this.cancelEvent(e)

if (this.selectedInstance) {
this.bridge.send('inspect-instance', this.selectedInstance.__VUE_DEVTOOLS_UID__)
}

this.stopSelecting()
}

/**
* Cancel a mouse event
* @param {MouseEvent} e
*/
cancelEvent (e) {
e.stopImmediatePropagation()
e.preventDefault()
}

/**
* Bind class methods to the class scope to avoid rebind for event listeners
*/
bindMethods () {
this.startSelecting = this.startSelecting.bind(this)
this.stopSelecting = this.stopSelecting.bind(this)
this.elementMouseOver = this.elementMouseOver.bind(this)
this.elementClicked = this.elementClicked.bind(this)
}
}
23 changes: 21 additions & 2 deletions src/backend/highlighter.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { inDoc } from '../util'
import { getInstanceName } from './index'

const overlay = document.createElement('div')
overlay.style.backgroundColor = 'rgba(104, 182, 255, 0.35)'
overlay.style.position = 'fixed'
overlay.style.zIndex = '99999999999999'
overlay.style.pointerEvents = 'none'
overlay.style.display = 'flex'
overlay.style.alignItems = 'center'
overlay.style.justifyContent = 'center'
overlay.style.borderRadius = '3px'
const overlayContent = document.createElement('div')
overlayContent.style.backgroundColor = 'rgba(104, 182, 255, 0.9)'
overlayContent.style.fontFamily = 'monospace'
overlayContent.style.fontSize = '11px'
overlayContent.style.padding = '2px 3px'
overlayContent.style.borderRadius = '3px'
overlayContent.style.color = 'white'
overlay.appendChild(overlayContent)

/**
* Highlight an instance.
Expand All @@ -16,7 +29,10 @@ export function highlight (instance) {
if (!instance) return
const rect = getInstanceRect(instance)
if (rect) {
showOverlay(rect)
let content = ''
const name = getInstanceName(instance)
name && (content = `<span style="opacity: .6;">&lt;</span>${name}<span style="opacity: .6;">&gt;</span>`)
showOverlay(rect, content)
}
}

Expand Down Expand Up @@ -107,11 +123,14 @@ function getTextRect (node) {
* @param {Rect}
*/

function showOverlay ({ width = 0, height = 0, top = 0, left = 0 }) {
function showOverlay ({ width = 0, height = 0, top = 0, left = 0 }, content = '') {
overlay.style.width = ~~width + 'px'
overlay.style.height = ~~height + 'px'
overlay.style.top = ~~top + 'px'
overlay.style.left = ~~left + 'px'

overlayContent.innerHTML = content

document.body.appendChild(overlay)
}

Expand Down
9 changes: 4 additions & 5 deletions src/backend/hook.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { findRelatedComponent } from './utils'

// this script is injected into every page.

/**
Expand Down Expand Up @@ -87,13 +89,10 @@ export function installHook (window) {
// Start recording context menu when Vue is detected
// event if Vue devtools are not loaded yet
document.addEventListener('contextmenu', event => {
let el = event.target
const el = event.target
if (el) {
// Search for parent that "is" a component instance
while (!el.__vue__ && el.parentElement) {
el = el.parentElement
}
const instance = el.__vue__
const instance = findRelatedComponent(el)
if (instance) {
window.__VUE_DEVTOOLS_CONTEXT_MENU_HAS_TARGET__ = true
window.__VUE_DEVTOOLS_CONTEXT_MENU_TARGET__ = instance
Expand Down
3 changes: 3 additions & 0 deletions src/backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { initVuexBackend } from './vuex'
import { initEventsBackend } from './events'
import { stringify, classify, camelize, set, parse } from '../util'
import path from 'path'
import ComponentSelector from './component-selector'

// Use a custom basename functions instead of the shimed version
// because it doesn't work on Windows
Expand Down Expand Up @@ -83,6 +84,8 @@ function connect () {

bridge.on('leave-instance', unHighlight)

new ComponentSelector(bridge, instanceMap)

// Get the instance id that is targeted by context menu
bridge.on('get-context-menu-target', () => {
const instance = window.__VUE_DEVTOOLS_CONTEXT_MENU_TARGET__
Expand Down
6 changes: 6 additions & 0 deletions src/backend/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function findRelatedComponent (el) {
while (!el.__vue__ && el.parentElement) {
el = el.parentElement
}
return el.__vue__
}
2 changes: 0 additions & 2 deletions src/devtools/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,6 @@ export default {
background-color $background-color
display flex
flex-direction column
h1
color #42b983
.dark &
background-color $dark-background-color
Expand Down
4 changes: 3 additions & 1 deletion src/devtools/components/ActionHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@
display inline
.material-icons
font-size 18px
font-size 16px
margin-right 0
position relative
top 1px
color inherit
@media (min-width: $wide)
margin-right 5px
Expand Down
4 changes: 4 additions & 0 deletions src/devtools/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ function initApp (shell) {
store.commit('components/RECEIVE_INSTANCE_DETAILS', parse(details))
})

bridge.on('toggle-instance', payload => {
store.commit('components/TOGGLE_INSTANCE', parse(payload))
})

bridge.on('vuex:init', snapshot => {
store.commit('vuex/INIT', snapshot)
})
Expand Down
23 changes: 10 additions & 13 deletions src/devtools/mixins/key-nav.js → src/devtools/mixins/keyboard.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
const navMap = {
37: 'left',
38: 'up',
39: 'right',
40: 'down'
}
export const LEFT = 37
export const UP = 38
export const RIGHT = 39
export const DOWN = 40
export const S = 83

const activeInstances = []

document.addEventListener('keyup', e => {
if (e.target.tagName === 'INPUT') {
return
}
if (navMap[e.keyCode]) {
activeInstances.forEach(vm => {
if (vm.onKeyNav) {
vm.onKeyNav(navMap[e.keyCode])
}
})
}
activeInstances.forEach(vm => {
if (vm.onKeyUp) {
vm.onKeyUp(e)
}
})
})

export default {
Expand Down
8 changes: 8 additions & 0 deletions src/devtools/transitions.styl
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@
transform rotate(0deg)
100%
transform rotate(360deg)

@keyframes pulse
0%
opacity 1
50%
opacity .2
100%
opacity 1
4 changes: 2 additions & 2 deletions src/devtools/variables.styl
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ $orange = #DB6B00
$black = #000000

// The min-width to give icons text...
$wide = 820px
$wide = 1050px

// The min-height to give the tools a little more breathing room...
$tall = 300px
$tall = 350px

// Theme
$active-color = $darkerGreen
Expand Down
Loading

0 comments on commit 1fe68b2

Please sign in to comment.