import { isBrowser } from '@vue-devtools/shared-utils' ; import { BackendContext , DevtoolsBackend } from '@vue-devtools/app-backend-api' ; import { ComponentBounds , ComponentInstance } from '@vue/devtools-api' ; import { JobQueue } from './util/queue' ; let overlay ; let overlayContent ; let currentInstance ; function createOverlay ( ) { if ( overlay || ! isBrowser ) return ; overlay = document . createElement ( 'div' ) ; overlay . style . backgroundColor = 'rgba(65, 184, 131, 0.35)' ; overlay . style . position = 'fixed' ; overlay . style . zIndex = '99999999999998' ; overlay . style . pointerEvents = 'none' ; overlay . style . borderRadius = '3px' ; overlayContent = document . createElement ( 'div' ) ; overlayContent . style . position = 'fixed' ; overlayContent . style . zIndex = '99999999999999' ; overlayContent . style . pointerEvents = 'none' ; overlayContent . style . backgroundColor = 'white' ; overlayContent . style . fontFamily = 'monospace' ; overlayContent . style . fontSize = '11px' ; overlayContent . style . padding = '4px 8px' ; overlayContent . style . borderRadius = '3px' ; overlayContent . style . color = '#333' ; overlayContent . style . textAlign = 'center' ; overlayContent . style . border = 'rgba(65, 184, 131, 0.5) 1px solid' ; overlayContent . style . backgroundClip = 'padding-box' ; } const jobQueue = new JobQueue ( ) ; export async function highlight ( instance , backend , ctx ) { await jobQueue . queue ( async ( ) => { if ( ! instance ) return ; const bounds = await backend . api . getComponentBounds ( instance ) ; if ( bounds ) { createOverlay ( ) ; const name = ( await backend . api . getComponentName ( instance ) ) || 'Anonymous' ; const pre = document . createElement ( 'span' ) ; pre . style . opacity = '0.6' ; pre . innerText = '<' ; const text = document . createElement ( 'span' ) ; text . style . fontWeight = 'bold' ; text . style . color = '#09ab56' ; text . innerText = name ; const post = document . createElement ( 'span' ) ; post . style . opacity = '0.6' ; post . innerText = '>' ; const size = document . createElement ( 'span' ) ; size . style . opacity = '0.5' ; size . style . marginLeft = '6px' ; size . appendChild ( document . createTextNode ( ( Math . round ( bounds . width * 100 ) / 100 ) . toString ( ) ) ) ; const multiply = document . createElement ( 'span' ) ; multiply . style . marginLeft = multiply . style . marginRight = '2px' ; multiply . innerText = '×' ; size . appendChild ( multiply ) ; size . appendChild ( document . createTextNode ( ( Math . round ( bounds . height * 100 ) / 100 ) . toString ( ) ) ) ; currentInstance = instance ; await showOverlay ( bounds , [ pre , text , post , size ] ) ; } startUpdateTimer ( backend , ctx ) ; } ) ; } export async function unHighlight ( ) { await jobQueue . queue ( async ( ) => { overlay ?. parentNode ?. removeChild ( overlay ) ; overlayContent ?. parentNode ?. removeChild ( overlayContent ) ; currentInstance = null ; stopUpdateTimer ( ) ; } ) ; } function showOverlay ( bounds , children = null ) { if ( ! isBrowser || ! children . length ) return ; positionOverlay ( bounds ) ; document . body . appendChild ( overlay ) ; overlayContent . innerHTML = '' ; children . forEach ( child => overlayContent . appendChild ( child ) ) ; document . body . appendChild ( overlayContent ) ; positionOverlayContent ( bounds ) ; } function positionOverlay ( { width = 0 , height = 0 , top = 0 , left = 0 } ) { overlay . style . width = Math . round ( width ) + 'px' ; overlay . style . height = Math . round ( height ) + 'px' ; overlay . style . left = Math . round ( left ) + 'px' ; overlay . style . top = Math . round ( top ) + 'px' ; } function positionOverlayContent ( { height = 0 , top = 0 , left = 0 } ) { const contentWidth = overlayContent . offsetWidth ; const contentHeight = overlayContent . offsetHeight ; let contentLeft = left ; if ( contentLeft < 0 ) { contentLeft = 0 ; } else if ( contentLeft + contentWidth > window . innerWidth ) { contentLeft = window . innerWidth - contentWidth ; } let contentTop = top - contentHeight - 2 ; if ( contentTop < 0 ) { contentTop = top + height + 2 ; } if ( contentTop < 0 ) { contentTop = 0 ; } else if ( contentTop + contentHeight > window . innerHeight ) { contentTop = window . innerHeight - contentHeight ; } overlayContent . style . left = ~ ~ contentLeft + 'px' ; overlayContent . style . top = ~ ~ contentTop + 'px' ; } async function updateOverlay ( backend , ctx ) { if ( currentInstance ) { const bounds = await backend . api . getComponentBounds ( currentInstance ) ; if ( bounds ) { const sizeEl = overlayContent . children . item ( 3 ) ; const widthEl = sizeEl . childNodes [ 0 ] as unknown as Text ; widthEl . textContent = ( Math . round ( bounds . width * 100 ) / 100 ) . toString ( ) ; const heightEl = sizeEl . childNodes [ 2 ] as unknown as Text ; heightEl . textContent = ( Math . round ( bounds . height * 100 ) / 100 ) . toString ( ) ; positionOverlay ( bounds ) ; positionOverlayContent ( bounds ) ; } } } let updateTimer ; function startUpdateTimer ( backend , ctx ) { stopUpdateTimer ( ) ; updateTimer = setInterval ( ( ) => { jobQueue . queue ( async ( ) => { await updateOverlay ( backend , ctx ) ; } ) ; } , 1000 / 30 ) ; } function stopUpdateTimer ( ) { clearInterval ( updateTimer ) ; }