import Tool from '../DevTools/Tool'
import $ from 'licia/$'
import isEl from 'licia/isEl'
import nextTick from 'licia/nextTick'
import Emitter from 'licia/Emitter'
import map from 'licia/map'
import MediaQuery from 'licia/MediaQuery'
import isEmpty from 'licia/isEmpty'
import toNum from 'licia/toNum'
import copy from 'licia/copy'
import isMobile from 'licia/isMobile'
import isShadowRoot from 'licia/isShadowRoot'
import LunaDomViewer from 'luna-dom-viewer'
import { isErudaEl, classPrefix as c, isChobitsuEl } from '../lib/util'
import evalCss from '../lib/evalCss'
import Detail from './Detail'
import chobitsu from '../lib/chobitsu'
import emitter from '../lib/emitter'
import { formatNodeName } from './util'
export default class Elements extends Tool {
constructor() {
super()
this._style = evalCss(require('./Elements.scss'))
this.name = 'elements'
this.displayName = 'エレメント'
this._selectElement = false
this._observeElement = true
this._history = []
Emitter.mixin(this)
}
init($el, container) {
super.init($el)
this._container = container
this._initTpl()
this._htmlEl = document.documentElement
this._detail = new Detail(this._$detail, container)
this.config = this._detail.config
this._splitMediaQuery = new MediaQuery('screen and (min-width: 680px)')
this._splitMode = this._splitMediaQuery.isMatch()
this._domViewer = new LunaDomViewer(this._$domViewer.get(0), {
node: this._htmlEl,
ignore: (node) => isErudaEl(node) || isChobitsuEl(node),
})
this._domViewer.expand()
this._bindEvent()
chobitsu.domain('Overlay').enable()
nextTick(() => this._updateHistory())
}
show() {
super.show()
this._isShow = true
if (!this._curNode) {
this.select(document.body)
} else if (this._splitMode) {
this._showDetail()
}
}
hide() {
super.hide()
this._isShow = false
chobitsu.domain('Overlay').hideHighlight()
}
select(node) {
this._domViewer.select(node)
this._setNode(node)
this.emit('change', node)
return this
}
destroy() {
super.destroy()
emitter.off(emitter.SCALE, this._updateScale)
evalCss.remove(this._style)
this._detail.destroy()
chobitsu
.domain('Overlay')
.off('inspectNodeRequested', this._inspectNodeRequested)
chobitsu.domain('Overlay').disable()
this._splitMediaQuery.removeAllListeners()
}
_updateButtons() {
const $control = this._$control
const $showDetail = $control.find(c('.show-detail'))
const $copyNode = $control.find(c('.copy-node'))
const $deleteNode = $control.find(c('.delete-node'))
const iconDisabled = c('icon-disabled')
$showDetail.addClass(iconDisabled)
$copyNode.addClass(iconDisabled)
$deleteNode.addClass(iconDisabled)
const node = this._curNode
if (!node || isShadowRoot(node)) {
return
}
if (node !== document.documentElement && node !== document.body) {
$deleteNode.rmClass(iconDisabled)
}
$copyNode.rmClass(iconDisabled)
if (node.nodeType === Node.ELEMENT_NODE) {
$showDetail.rmClass(iconDisabled)
}
}
_showDetail = () => {
if (!this._isShow || !this._curNode) {
return
}
if (this._curNode.nodeType === Node.ELEMENT_NODE) {
this._detail.show(this._curNode)
} else {
this._detail.show(this._curNode.parentNode || this._curNode.host)
}
}
_initTpl() {
const $el = this._$el
$el.html(
c(`
`)
)
this._$detail = $el.find(c('.detail'))
this._$domViewer = $el.find(c('.dom-viewer'))
this._$control = $el.find(c('.control'))
this._$crumbs = $el.find(c('.crumbs'))
}
_renderCrumbs() {
const crumbs = getCrumbs(this._curNode)
let html = ''
if (!isEmpty(crumbs)) {
html = map(crumbs, ({ text, idx }) => {
return `${text}`
}).join('')
}
this._$crumbs.html(html)
}
_back = () => {
if (this._curNode === this._htmlEl) return
const parentQueue = this._curParentQueue
let parent = parentQueue.shift()
while (!isElExist(parent)) {
parent = parentQueue.shift()
}
this.set(parent)
}
_bindEvent() {
const self = this
this._$el.on('click', c('.crumb'), function () {
let idx = toNum($(this).data('idx'))
let node = self._curNode
while (idx-- && node.parentElement) {
node = node.parentElement
}
if (isElExist(node)) {
self.select(node)
}
})
this._$control
.on('click', c('.select'), this._toggleSelect)
.on('click', c('.show-detail'), this._showDetail)
.on('click', c('.copy-node'), this._copyNode)
.on('click', c('.delete-node'), this._deleteNode)
this._domViewer.on('select', this._setNode).on('deselect', this._back)
chobitsu
.domain('Overlay')
.on('inspectNodeRequested', this._inspectNodeRequested)
this._splitMediaQuery.on('match', () => {
this._splitMode = true
this._showDetail()
})
this._splitMediaQuery.on('unmatch', () => {
this._splitMode = false
this._detail.hide()
})
emitter.on(emitter.SCALE, this._updateScale)
}
_updateScale = (scale) => {
this._splitMediaQuery.setQuery(`screen and (min-width: ${680 * scale}px)`)
}
_deleteNode = () => {
const node = this._curNode
if (node.parentNode) {
node.parentNode.removeChild(node)
}
}
_copyNode = () => {
const node = this._curNode
if (node.nodeType === Node.ELEMENT_NODE) {
copy(node.outerHTML)
} else {
copy(node.nodeValue)
}
this._container.notify('コピーしました', { icon: 'success' })
}
_toggleSelect = () => {
this._$el.find(c('.select')).toggleClass(c('active'))
this._selectElement = !this._selectElement
if (this._selectElement) {
chobitsu.domain('Overlay').setInspectMode({
mode: 'searchForNode',
highlightConfig: {
showInfo: !isMobile(),
showRulers: false,
showAccessibilityInfo: !isMobile(),
showExtensionLines: false,
contrastAlgorithm: 'aa',
contentColor: 'rgba(111, 168, 220, .66)',
paddingColor: 'rgba(147, 196, 125, .55)',
borderColor: 'rgba(255, 229, 153, .66)',
marginColor: 'rgba(246, 178, 107, .66)',
},
})
this._container.hide()
} else {
chobitsu.domain('Overlay').setInspectMode({
mode: 'none',
})
chobitsu.domain('Overlay').hideHighlight()
}
}
_inspectNodeRequested = ({ backendNodeId }) => {
this._container.show()
this._toggleSelect()
try {
const { node } = chobitsu.domain('DOM').getNode({ nodeId: backendNodeId })
this.select(node)
} catch {
// No op
}
}
_setNode = (node) => {
if (node === this._curNode) return
this._curNode = node
this._renderCrumbs()
const parentQueue = []
let parent = node.parentNode
while (parent) {
parentQueue.push(parent)
parent = parent.parentNode
}
this._curParentQueue = parentQueue
if (this._splitMode) {
this._showDetail()
}
this._updateButtons()
this._updateHistory()
}
_updateHistory() {
const console = this._container.get('console')
if (!console) return
const history = this._history
history.unshift(this._curNode)
if (history.length > 5) history.pop()
for (let i = 0; i < 5; i++) {
console.setGlobal(`$${i}`, history[i])
}
}
}
const isElExist = (val) => isEl(val) && val.parentNode
function getCrumbs(el) {
const ret = []
let i = 0
while (el) {
ret.push({
text: formatNodeName(el, { noAttr: true }),
idx: i++,
})
if (isShadowRoot(el)) {
el = el.host
}
if (!el.parentElement && isShadowRoot(el.parentNode)) {
el = el.parentNode
} else {
el = el.parentElement
}
}
return ret.reverse()
}