File size: 3,456 Bytes
4d70170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import { BridgeEvents, isBrowser } from '@vue-devtools/shared-utils'
import type { BackendContext, DevtoolsBackend } from '@vue-devtools/app-backend-api'
import type { ComponentInstance } from '@vue/devtools-api'
import { highlight, unHighlight } from './highlighter'

export default class ComponentPicker {
  ctx: BackendContext
  selectedInstance: ComponentInstance
  selectedBackend: DevtoolsBackend

  constructor(ctx: BackendContext) {
    this.ctx = ctx
    this.bindMethods()
  }

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

  /**
   * Removes event listeners
   */
  stopSelecting() {
    if (!isBrowser) {
      return
    }
    window.removeEventListener('mouseover', this.elementMouseOver, true)
    window.removeEventListener('click', this.elementClicked, true)
    window.removeEventListener('mouseout', this.cancelEvent, true)
    window.removeEventListener('mouseenter', this.cancelEvent, true)
    window.removeEventListener('mouseleave', this.cancelEvent, true)
    window.removeEventListener('mousedown', this.cancelEvent, true)
    window.removeEventListener('mouseup', this.cancelEvent, true)

    unHighlight()
  }

  /**
   * Highlights a component on element mouse over
   */
  async elementMouseOver(e: MouseEvent) {
    this.cancelEvent(e)

    const el = e.target
    if (el) {
      await this.selectElementComponent(el)
    }

    unHighlight()
    if (this.selectedInstance) {
      highlight(this.selectedInstance, this.selectedBackend, this.ctx)
    }
  }

  async selectElementComponent(el) {
    for (const backend of this.ctx.backends) {
      const instance = await backend.api.getElementComponent(el)
      if (instance) {
        this.selectedInstance = instance
        this.selectedBackend = backend
        return
      }
    }
    this.selectedInstance = null
    this.selectedBackend = null
  }

  /**
   * Selects an instance in the component view
   */
  async elementClicked(e: MouseEvent) {
    this.cancelEvent(e)

    if (this.selectedInstance && this.selectedBackend) {
      const parentInstances = await this.selectedBackend.api.walkComponentParents(this.selectedInstance)
      this.ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_PICK, { id: this.selectedInstance.__VUE_DEVTOOLS_UID__, parentIds: parentInstances.map(i => i.__VUE_DEVTOOLS_UID__) })
    }
    else {
      this.ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_PICK_CANCELED, null)
    }

    this.stopSelecting()
  }

  /**
   * Cancel a mouse event
   */
  cancelEvent(e: MouseEvent) {
    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)
  }
}