1
0
mirror of synced 2025-11-06 04:21:11 +08:00

feat(elements): element crumbs

This commit is contained in:
redhoodsu
2022-12-17 12:19:09 +08:00
parent 7f7b28a4cb
commit 025c8cea6e
5 changed files with 156 additions and 249 deletions

View File

@@ -83,16 +83,16 @@ Check dom element status.
|overrideEventTarget|boolean|Catch Event Listeners|
|observeElement |boolean|Auto Refresh |
### set
### select
Set dom element to show.
Select node to show.
|Name|Type |Desc |
|----|-------|------------------|
|el |element|Element to display|
|Name|Type |Desc |
|----|---------|-------------------|
|node|ChildNode|Node to be selected|
```javascript
elements.set(document.body);
elements.select(document.body);
```
## Network

View File

@@ -12,39 +12,32 @@ import isNaN from 'licia/isNaN'
import isNum from 'licia/isNum'
import each from 'licia/each'
import keys from 'licia/keys'
import isNull from 'licia/isNull'
import trim from 'licia/trim'
import CssStore from './CssStore'
import LunaModal from 'luna-modal'
import { formatNodeName } from './util'
import { pxToNum, classPrefix as c } from '../lib/util'
export default class Detail {
constructor($container) {
this._$container = $container
this._curEl = document.documentElement
this._bindEvent()
}
show(el) {
const data = this._getData(el)
this._curEl = el
this._rmDefComputedStyle = true
this._computedStyleSearchKeyword = ''
this._render()
}
_toggleAllComputedStyle() {
this._rmDefComputedStyle = !this._rmDefComputedStyle
let parents = ''
if (!isEmpty(data.parents)) {
parents = `<ul class="${c('parents')}">
${map(data.parents, ({ text, idx }) => {
return `<li>
<div class="${c('parent')}" data-idx="${idx}">${text}</div>
<span class="${c('icon-arrow-right')}"></span>
</li>`
}).join('')}
</ul>`
}
let children = ''
if (data.children) {
children = `<ul class="${c('children')}">
${map(data.children, ({ isCmt, idx, isEl, text }) => {
return `<li class="${c(
`child ${isCmt ? 'green' : ''} ${isEl ? 'active-effect' : ''}`
)}" data-idx="${idx}">${text}</li>`
}).join('')}
</ul>`
}
this._render()
}
_render() {
const data = this._getData(this._curEl)
let attribute = '<tr><td>Empty</td></tr>'
if (!isEmpty(data.attributes)) {
@@ -177,11 +170,9 @@ export default class Detail {
</div>`
}
const html = `${parents}
<div class="${c('breadcrumb')}">
const html = `<div class="${c('breadcrumb')}">
${data.name}
</div>
${children}
${attribute}
${styles}
${computedStyle}
@@ -197,10 +188,8 @@ export default class Detail {
const { className, id, attributes, tagName } = el
ret.computedStyleSearchKeyword = this._computedStyleSearchKeyword
ret.parents = getParents(el)
ret.children = formatChildNodes(el.childNodes)
ret.attributes = formatAttr(attributes)
ret.name = formatElName({ tagName, id, className, attributes })
ret.name = formatNodeName({ tagName, id, className, attributes })
const events = el.erudaEvents
if (events && keys(events).length !== 0) ret.listeners = events
@@ -260,7 +249,20 @@ export default class Detail {
return ret
}
_bindEvent() {}
_bindEvent() {
this._$container
.on('click', c('.toggle-all-computed-style'), () =>
this._toggleAllComputedStyle()
)
.on('click', c('.computed-style-search'), () => {
LunaModal.prompt('Filter').then((filter) => {
if (isNull(filter)) return
filter = trim(filter)
this._computedStyleSearchKeyword = filter
this._render()
})
})
}
}
function processStyleRules(style) {
@@ -281,42 +283,6 @@ const formatAttr = (attributes) =>
return { name, value }
})
function formatChildNodes(nodes) {
const ret = []
for (let i = 0, len = nodes.length; i < len; i++) {
const child = nodes[i]
const nodeType = child.nodeType
if (nodeType === 3 || nodeType === 8) {
const val = child.nodeValue.trim()
if (val !== '')
ret.push({
text: val,
isCmt: nodeType === 8,
idx: i,
})
continue
}
const isSvg = !isStr(child.className)
if (
nodeType === 1 &&
child.id !== 'eruda' &&
(isSvg || child.className.indexOf('eruda') < 0)
) {
ret.push({
text: formatElName(child),
isEl: true,
idx: i,
})
}
}
return ret
}
const regColor = /rgba?\((.*?)\)/g
const regCssUrl = /url\("?(.*?)"?\)/g
@@ -332,50 +298,6 @@ function processStyleRule(val) {
.replace(regCssUrl, (match, url) => `url("${wrapLink(url)}")`)
}
function formatElName(data, { noAttr = false } = {}) {
const { id, className, attributes } = data
let ret = `<span class="eruda-tag-name-color">${data.tagName.toLowerCase()}</span>`
if (id !== '') ret += `<span class="eruda-function-color">#${id}</span>`
if (isStr(className)) {
let classes = ''
each(className.split(/\s+/g), (val) => {
if (val.trim() === '') return
classes += `.${val}`
})
ret += `<span class="eruda-attribute-name-color">${classes}</span>`
}
if (!noAttr) {
each(attributes, (attr) => {
const name = attr.name
if (name === 'id' || name === 'class' || name === 'style') return
ret += ` <span class="eruda-attribute-name-color">${name}</span><span class="eruda-operator-color">="</span><span class="eruda-string-color">${attr.value}</span><span class="eruda-operator-color">"</span>`
})
}
return ret
}
function getParents(el) {
const ret = []
let i = 0
let parent = el.parentNode
while (parent && parent.nodeType === 1) {
ret.push({
text: formatElName(parent, { noAttr: true }),
idx: i++,
})
parent = parent.parentNode
}
return ret.reverse()
}
function getInlineStyle(style) {
const ret = {
selectorText: 'element.style',

View File

@@ -12,13 +12,14 @@ import isBool from 'licia/isBool'
import safeGet from 'licia/safeGet'
import nextTick from 'licia/nextTick'
import Emitter from 'licia/Emitter'
import isNull from 'licia/isNull'
import trim from 'licia/trim'
import LunaModal from 'luna-modal'
import map from 'licia/map'
import isEmpty from 'licia/isEmpty'
import toNum from 'licia/toNum'
import LunaDomViewer from 'luna-dom-viewer'
import { isErudaEl, classPrefix as c } from '../lib/util'
import evalCss from '../lib/evalCss'
import Detail from './Detail'
import { formatNodeName } from './util'
export default class Elements extends Tool {
constructor() {
@@ -27,11 +28,9 @@ export default class Elements extends Tool {
this._style = evalCss(require('./Elements.scss'))
this.name = 'elements'
this._rmDefComputedStyle = true
this._highlightElement = false
this._selectElement = false
this._observeElement = true
this._computedStyleSearchKeyword = ''
this._history = []
Emitter.mixin(this)
@@ -62,22 +61,19 @@ export default class Elements extends Tool {
if (this._observeElement) this._enableObserver()
if (!this._curEl) this._setEl(this._htmlEl)
this._render()
}
hide() {
this._disableObserver()
return super.hide()
}
set(e) {
if (e === this._curEl) return
this._setEl(e)
this._render()
this._updateHistory()
this.emit('change', e)
// To be removed in 3.0.0
set(node) {
return this.select(node)
}
select(node) {
this._setEl(node)
this.emit('change', node)
return this
}
overrideEventTarget() {
@@ -117,18 +113,30 @@ export default class Elements extends Tool {
$el.html(
c(`<div class="control">
<span class="icon icon-select select"></span>
<span class="icon icon-eye show"></span>
</div>
<div class="dom-viewer-container">
<div class="dom-viewer"></div>
</div>
<div class="detail"></div>`)
<span class="icon icon-select select"></span>
<span class="icon icon-eye show"></span>
</div>
<div class="dom-viewer-container">
<div class="dom-viewer"></div>
</div>
<div class="crumbs"></div>
<div class="detail"></div>`)
)
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._curEl)
let html = ''
if (!isEmpty(crumbs)) {
html = map(crumbs, ({ text, idx }) => {
return `<li class="${c('crumb')}" data-idx="${idx}">${text}</div></li>`
}).join('')
}
this._$crumbs.html(html)
}
_back() {
if (this._curEl === this._htmlEl) return
@@ -146,38 +154,6 @@ export default class Elements extends Tool {
const select = this._select
this._$el
.on('click', '.eruda-child', function () {
const idx = $(this).data('idx')
const curEl = self._curEl
const el = curEl.childNodes[idx]
if (el && el.nodeType === 3) {
const curTagName = curEl.tagName
let type
switch (curTagName) {
case 'SCRIPT':
type = 'js'
break
case 'STYLE':
type = 'css'
break
default:
return
}
const sources = container.get('sources')
if (sources) {
sources.set(type, el.nodeValue)
container.showTool('sources')
}
return
}
!isElExist(el) ? self._render() : self.set(el)
})
.on('click', '.eruda-listener-content', function () {
const text = $(this).text()
const sources = container.get('sources')
@@ -195,25 +171,17 @@ export default class Elements extends Tool {
container.showTool('sources')
}
})
.on('click', '.eruda-parent', function () {
let idx = $(this).data('idx')
const curEl = self._curEl
let el = curEl.parentNode
.on('click', c('.crumb'), function () {
let idx = toNum($(this).data('idx'))
let el = self._curEl
while (idx-- && el.parentNode) el = el.parentNode
while (idx-- && el.parentElement) {
el = el.parentElement
}
!isElExist(el) ? self._render() : self.set(el)
})
.on('click', '.eruda-toggle-all-computed-style', () =>
this._toggleAllComputedStyle()
)
.on('click', '.eruda-computed-style-search', () => {
LunaModal.prompt('Filter').then((filter) => {
if (isNull(filter)) return
filter = trim(filter)
this._computedStyleSearchKeyword = filter
this._render()
})
if (isElExist(el)) {
self.set(el)
}
})
this._$control
@@ -221,11 +189,8 @@ export default class Elements extends Tool {
.on('click', c('.show'), () => this._detail.show(this._curEl))
select.on('select', (target) => this.set(target))
}
_toggleAllComputedStyle() {
this._rmDefComputedStyle = !this._rmDefComputedStyle
this._render()
this._domViewer.on('select', this._setEl)
}
_enableObserver() {
this._observer.observe(this._htmlEl, {
@@ -260,11 +225,14 @@ export default class Elements extends Tool {
select.disable()
}
}
_setEl(el) {
_setEl = (el) => {
if (el === this._curEl) return
this._curEl = el
this._domViewer.select(el)
this._renderCrumbs()
this._highlight.setEl(el)
this._rmDefComputedStyle = true
const parentQueue = []
@@ -274,16 +242,8 @@ export default class Elements extends Tool {
parent = parent.parentNode
}
this._curParentQueue = parentQueue
}
_render() {
if (!isElExist(this._curEl)) return this._back()
this._highlight[this._highlightElement ? 'show' : 'hide']()
}
_renderHtml(html) {
if (html === this._lastHtml) return
this._lastHtml = html
this._$showArea.html(html)
this._updateHistory()
}
_updateHistory() {
const console = this._container.get('console')
@@ -409,3 +369,19 @@ const getWinEventProto = () => {
}
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++,
})
el = el.parentElement
}
return ret.reverse()
}

View File

@@ -3,6 +3,7 @@
#elements {
padding-top: 40px;
padding-bottom: 24px;
font-size: 14px;
.control {
@include control();
@@ -16,32 +17,24 @@
height: 100%;
padding: 5px 0;
}
.detail {
@include absolute();
@include overflow-auto(y);
z-index: 10;
display: none;
background: var(--background);
}
.parents {
@include overflow-auto(x);
overflow-y: hidden;
.crumbs {
@include absolute(100%, 24px);
top: initial;
line-height: 24px;
bottom: 0;
border-top: 1px solid var(--border);
background: var(--darker-background);
color: var(--primary);
padding: 0 $padding;
height: 40px;
line-height: 40px;
font-size: $font-size-s;
white-space: nowrap;
border-bottom: 1px solid var(--border);
cursor: pointer;
font-size: $font-size;
overflow: hidden;
li {
cursor: pointer;
padding: 0 7px;
display: inline-block;
.parent {
display: inline-block;
}
&:hover,
&:last-child {
margin-right: 0;
background: var(--highlight);
}
}
.icon-arrow-right {
@@ -50,6 +43,13 @@
top: 1px;
}
}
.detail {
@include absolute();
@include overflow-auto(y);
z-index: 10;
display: none;
background: var(--background);
}
.breadcrumb {
@include breadcrumb();
cursor: pointer;
@@ -83,33 +83,6 @@
}
margin-bottom: 10px;
}
.children {
background: var(--darker-background);
color: var(--foreground);
margin-bottom: 10px !important;
border-bottom: 1px solid var(--border);
li {
@include overflow-auto(x);
cursor: default;
padding: $padding;
border-top: 1px solid var(--border);
white-space: nowrap;
transition: background $anim-duration, color $anim-duration;
span {
transition: color $anim-duration;
}
&.active-effect {
cursor: pointer;
}
&.active-effect:active {
background: var(--highlight);
color: var(--select-foreground);
span {
color: var(--select-foreground);
}
}
}
}
.attributes {
font-size: $font-size-s;
a {

36
src/Elements/util.js Normal file
View File

@@ -0,0 +1,36 @@
import each from 'licia/each'
import isStr from 'licia/isStr'
import { classPrefix as c } from '../lib/util'
export function formatNodeName(node, { noAttr = false } = {}) {
if (node.nodeType === Node.TEXT_NODE) {
return `<span class="${c('tag-name-color')}">(text)</span>`
} else if (node.nodeType === Node.COMMENT_NODE) {
return `<span class="${c('tag-name-color')}"><!--></span>`
}
const { id, className, attributes } = node
let ret = `<span class="eruda-tag-name-color">${node.tagName.toLowerCase()}</span>`
if (id !== '') ret += `<span class="eruda-function-color">#${id}</span>`
if (isStr(className)) {
let classes = ''
each(className.split(/\s+/g), (val) => {
if (val.trim() === '') return
classes += `.${val}`
})
ret += `<span class="eruda-attribute-name-color">${classes}</span>`
}
if (!noAttr) {
each(attributes, (attr) => {
const name = attr.name
if (name === 'id' || name === 'class' || name === 'style') return
ret += ` <span class="eruda-attribute-name-color">${name}</span><span class="eruda-operator-color">="</span><span class="eruda-string-color">${attr.value}</span><span class="eruda-operator-color">"</span>`
})
}
return ret
}