chore: use luna object viewer

This commit is contained in:
redhoodsu
2020-03-20 16:49:57 +08:00
parent ab511fd568
commit 40e7f0ae38
11 changed files with 60 additions and 780 deletions

View File

@@ -16,7 +16,8 @@ const postcssLoader = {
options: {
plugins: [
prefixer({
prefix: '_'
prefix: '_',
ignore: [/luna-object-viewer/]
}),
autoprefixer,
clean()

View File

@@ -61,6 +61,7 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^4.0.2",
"licia": "^1.19.0",
"luna-object-viewer": "^0.1.1",
"node-sass": "^4.13.1",
"postcss-clean": "^1.1.0",
"postcss-loader": "^3.0.0",

View File

@@ -1,7 +1,6 @@
import origGetAbstract from '../lib/getAbstract'
import beautify from 'js-beautify'
import JsonViewer from '../lib/JsonViewer'
import ObjViewer from '../lib/ObjViewer'
import LunaObjectViewer from 'luna-object-viewer'
import {
isObj,
isStr,
@@ -210,16 +209,18 @@ export default class Log extends Emitter {
if ($json.hasClass('eruda-hidden')) {
if ($json.data('init') !== 'true') {
if (src) {
const jsonViewer = new JsonViewer(src, $json)
jsonViewer.on('change', () => this.updateSize(false))
const staticViewer = new LunaObjectViewer.Static($json.get(0))
staticViewer.set(src)
staticViewer.on('change', () => this.updateSize(false))
} else {
if (type === 'table' || args.length === 1) {
if (isObj(args[0])) args = args[0]
}
const objViewer = new ObjViewer(args, $json, {
showUnenumerable: Log.showUnenumerable,
showGetterVal: Log.showGetterVal
const objViewer = new LunaObjectViewer($json.get(0), {
unenumerable: Log.showUnenumerable,
accessGetter: Log.showGetterVal
})
objViewer.set(args)
objViewer.on('change', () => this.updateSize(false))
}
$json.data('init', 'true')

View File

@@ -1,6 +1,6 @@
import Tool from '../DevTools/Tool'
import beautify from 'js-beautify'
import ObjViewer from '../lib/ObjViewer'
import LunaObjectViewer from 'luna-object-viewer'
import Settings from '../Settings/Settings'
import { ajax, escape, trim, isStr, highlight } from '../lib/util'
import evalCss from '../lib/evalCss'
@@ -247,10 +247,14 @@ export default class Sources extends Tool {
/* eslint-disable no-empty */
} catch (e) {}
new ObjViewer(val, this._$el.find('.eruda-json'), {
showUnenumerable: true,
showGetterVal: true
})
const objViewer = new LunaObjectViewer(
this._$el.find('.eruda-json').get(0),
{
unenumerable: true,
accessGetter: true
}
)
objViewer.set(val)
}
_renderRaw() {
this._renderHtml(this._rawTpl({ val: this._data.val }))

View File

@@ -198,6 +198,7 @@ export default {
}
evalCss(
require('luna-object-viewer/luna-object-viewer.css') +
require('./style/style.scss') +
require('./style/reset.scss') +
require('./style/icon.css')

View File

@@ -1,338 +0,0 @@
import {
$,
startWith,
isObj,
uniqId,
upperFirst,
toNum,
toStr,
escape,
chunk,
each,
isNaN,
isNum,
isBool,
keys,
trim,
lowerCase,
Emitter
} from './util'
import evalCss from './evalCss'
let hasEvalCss = false
export default class JsonViewer extends Emitter {
constructor(data, $el) {
super()
if (!hasEvalCss) {
evalCss(require('./json.scss'))
hasEvalCss = true
}
this._data = {
id: uniqId('json'),
enumerable: {
0: data
}
}
this._$el = $el
this._map = {}
createMap(this._map, this._data)
this._appendTpl()
this._bindEvent()
}
_jsonToHtml(data, firstLevel) {
let ret = ''
each(['enumerable', 'unenumerable', 'symbol'], type => {
if (!data[type]) return
const typeKeys = keys(data[type])
typeKeys.sort(sortObjName)
for (let i = 0, len = typeKeys.length; i < len; i++) {
const key = typeKeys[i]
ret += this._createEl(key, data[type][key], type, firstLevel)
}
})
if (data.proto) {
if (ret === '') {
ret = this._jsonToHtml(data.proto)
} else {
ret += this._createEl('__proto__', data.proto, 'proto')
}
}
return ret
}
_createEl(key, val, keyType, firstLevel = false) {
let type = typeof val
if (val === null) {
return `<li>${wrapKey(key)}<span class="eruda-null">null</span></li>`
} else if (isNum(val) || isBool(val)) {
return `<li>${wrapKey(key)}<span class="eruda-${type}">${encode(
val
)}</span></li>`
}
if (val.type === 'RegExp') type = 'regexp'
if (val.type === 'Number') type = 'number'
if (val.type === 'Number' || val.type === 'RegExp') {
return `<li>${wrapKey(key)}<span class="eruda-${type}">${encode(
val.value
)}</span></li>`
} else if (val.type === 'Undefined' || val.type === 'Symbol') {
return `<li>${wrapKey(key)}<span class="eruda-special">${lowerCase(
val.type
)}</span></li>`
} else if (val === '(...)') {
return `<li>${wrapKey(key)}<span class="eruda-special">${val}</span></li>`
} else if (isObj(val)) {
const id = val.id
const referenceId = val.reference
const objAbstract = getObjAbstract(val) || upperFirst(type)
let obj = `<li ${
firstLevel ? 'data-first-level="true"' : ''
} ${'data-object-id="' + (referenceId || id) + '"'}><span class="${
firstLevel ? '' : 'eruda-expanded eruda-collapsed'
}"></span>${wrapKey(key)}<span class="eruda-open">${
firstLevel ? '' : objAbstract
}</span><ul class="eruda-${type}" ${
firstLevel ? '' : 'style="display:none"'
}>`
if (firstLevel) obj += this._jsonToHtml(this._map[id])
return obj + '</ul><span class="eruda-close"></span></li>'
}
function wrapKey(key) {
if (firstLevel) return ''
if (isObj(val) && val.jsonSplitArr) return ''
let keyClass = 'eruda-key'
if (
keyType === 'unenumerable' ||
keyType === 'proto' ||
keyType === 'symbol'
) {
keyClass = 'eruda-key-lighter'
}
return `<span class="${keyClass}">${encode(key)}</span>: `
}
return `<li>${wrapKey(key)}<span class="eruda-${typeof val}">"${encode(
val
)}"</span></li>`
}
_appendTpl() {
const data = this._map[this._data.id]
this._$el.html(this._jsonToHtml(data, true))
}
_bindEvent() {
const map = this._map
const self = this
this._$el.on('click', 'li', function(e) {
const $this = $(this)
const circularId = $this.data('object-id')
const $firstSpan = $(this)
.find('span')
.eq(0)
if ($this.data('first-level')) return
if (circularId) {
$this.find('ul').html(self._jsonToHtml(map[circularId], false))
$this.rmAttr('data-object-id')
}
e.stopImmediatePropagation()
if (!$firstSpan.hasClass('eruda-expanded')) return
const $ul = $this.find('ul').eq(0)
if ($firstSpan.hasClass('eruda-collapsed')) {
$firstSpan.rmClass('eruda-collapsed')
$ul.show()
} else {
$firstSpan.addClass('eruda-collapsed')
$ul.hide()
}
self.emit('change')
})
}
}
function createMap(map, data) {
const id = data.id
if (!id && id !== 0) return
const isArr = data.type && startWith(data.type, 'Array')
if (isArr && data.enumerable) {
const arr = objToArr(data, id, data.type)
if (arr.length > 100) data = splitBigArr(arr)
}
map[id] = data
const values = []
each(['enumerable', 'unenumerable', 'symbol'], type => {
if (!data[type]) return
for (const key in data[type]) {
values.push(data[type][key])
}
})
if (data.proto) {
values.push(data.proto)
}
for (let i = 0, len = values.length; i < len; i++) {
const val = values[i]
if (isObj(val)) createMap(map, val)
}
}
function splitBigArr(data) {
let idx = 0
const enumerable = {}
each(chunk(data, 100), val => {
const obj = {}
const startIdx = idx
obj.type = '[' + startIdx
obj.enumerable = {}
each(val, val => {
obj.enumerable[idx] = val
idx += 1
})
const endIdx = idx - 1
obj.type += (endIdx - startIdx > 0 ? ' … ' + endIdx : '') + ']'
obj.id = uniqId('json')
obj.jsonSplitArr = true
enumerable[idx] = obj
})
const ret = {}
ret.enumerable = enumerable
ret.id = data.id
ret.type = data.type
if (data.unenumerable) ret.unenumerable = data.unenumerable
if (data.symbol) ret.symbol = data.symbol
if (data.proto) ret.proto = data.proto
return ret
}
function objToArr(data, id, type) {
const ret = []
const enumerable = {}
each(data.enumerable, (val, key) => {
const idx = toNum(key)
if (!isNaN(idx)) {
ret[idx] = val
} else {
enumerable[key] = val
}
})
ret.enumerable = enumerable
ret.type = type
ret.id = id
if (data.unenumerable) ret.unenumerable = data.unenumerable
if (data.symbol) ret.symbol = data.symbol
if (data.proto) ret.proto = data.proto
return ret
}
export const encode = str => {
return escape(toStr(str))
.replace(/\n/g, '↵')
.replace(/\f|\r|\t/g, '')
}
// $, upperCase, lowerCase, _
export function sortObjName(a, b) {
a = toStr(a)
b = toStr(b)
const numA = toNum(a)
const numB = toNum(b)
if (!isNaN(numA) && !isNaN(numB)) {
if (numA > numB) return 1
if (numA < numB) return -1
return 0
}
if (startWith(a, 'get ') || startWith(a, 'set ')) a = a.slice(4)
if (startWith(b, 'get ') || startWith(b, 'set ')) b = b.slice(4)
const lenA = a.length
const lenB = b.length
const len = lenA > lenB ? lenB : lenA
for (let i = 0; i < len; i++) {
const codeA = a.charCodeAt(i)
const codeB = b.charCodeAt(i)
const cmpResult = cmpCode(codeA, codeB)
if (cmpResult !== 0) return cmpResult
}
if (lenA > lenB) return 1
if (lenA < lenB) return -1
return 0
}
function cmpCode(a, b) {
a = transCode(a)
b = transCode(b)
if (a > b) return 1
if (a < b) return -1
return 0
}
function transCode(code) {
// _ should be placed after lowercase chars.
if (code === 95) return 123
return code
}
export function getObjAbstract(data) {
const { type, value } = data
if (!type) return
if (type === 'Function') {
return getFnAbstract(value)
}
if (type === 'Array' && data.unenumerable) {
return `Array(${data.unenumerable.length})`
}
return data.type
}
const regFnHead = /function(.*?)\((.*?)\)/
function extractFnHead(str) {
const fnHead = str.match(regFnHead)
if (fnHead) return fnHead[0]
return str
}
export function getFnAbstract(str) {
if (str.length > 500) str = str.slice(0, 500) + '...'
return 'ƒ ' + trim(extractFnHead(str).replace('function', ''))
}

View File

@@ -1,341 +0,0 @@
import {
extend,
Emitter,
getProto,
isNum,
isBool,
lowerCase,
isObj,
isArr,
upperFirst,
keys,
each,
toSrc,
isPromise,
type,
$,
difference,
allKeys,
filter,
chunk
} from './util'
import { encode, getFnAbstract, sortObjName } from './JsonViewer'
import evalCss from './evalCss'
let hasEvalCss = false
export default class ObjViewer extends Emitter {
constructor(
data,
$el,
{ showUnenumerable = false, showGetterVal = false } = {}
) {
super()
if (!hasEvalCss) {
evalCss(require('./json.scss'))
hasEvalCss = true
}
this._data = [data]
this._$el = $el
this._visitor = new Visitor()
this._map = {}
this._showUnenumerable = showUnenumerable
this._showGetterVal = showGetterVal
this._appendTpl()
this._bindEvent()
}
_objToHtml(data, firstLevel) {
const visitor = this._visitor
let self = data
let isBigArr = false
const visitedObj = visitor.get(data)
if (visitedObj && visitedObj.self) {
self = visitedObj.self
}
let ret = ''
const types = ['enumerable']
let enumerableKeys = keys(data)
let unenumerableKeys = []
let symbolKeys = []
let virtualKeys = []
const virtualData = {}
if (this._showUnenumerable && !firstLevel) {
types.push('unenumerable')
types.push('symbol')
unenumerableKeys = difference(
allKeys(data, {
prototype: false,
unenumerable: true
}),
enumerableKeys
)
symbolKeys = filter(
allKeys(data, {
prototype: false,
symbol: true
}),
key => {
return typeof key === 'symbol'
}
)
}
if (isArr(data) && data.length > 100) {
types.unshift('virtual')
isBigArr = true
let idx = 0
const map = {}
each(chunk(data, 100), val => {
const obj = Object.create(null)
const startIdx = idx
let key = '[' + startIdx
each(val, val => {
obj[idx] = val
map[idx] = true
idx++
})
const endIdx = idx - 1
key += (endIdx - startIdx > 0 ? ' … ' + endIdx : '') + ']'
virtualData[key] = obj
})
virtualKeys = keys(virtualData)
enumerableKeys = filter(enumerableKeys, val => !map[val])
}
each(types, type => {
let typeKeys = []
if (type === 'symbol') {
typeKeys = symbolKeys
} else if (type === 'unenumerable') {
typeKeys = unenumerableKeys
} else if (type === 'virtual') {
typeKeys = virtualKeys
} else {
typeKeys = enumerableKeys
}
if (!isBigArr) {
typeKeys.sort(sortObjName)
}
for (let i = 0, len = typeKeys.length; i < len; i++) {
const key = typeKeys[i]
let val = ''
const descriptor = Object.getOwnPropertyDescriptor(data, key)
const hasGetter = descriptor && descriptor.get
const hasSetter = descriptor && descriptor.set
if (hasGetter && !this._showGetterVal) {
val = '(...)'
} else {
try {
if (type === 'virtual') {
val = virtualData[key]
} else {
val = self[key]
}
if (isPromise(val)) {
val.catch(() => {})
}
} catch (e) {
val = e.message
}
}
ret += this._createEl(key, data, val, type, firstLevel)
if (hasGetter) {
ret += this._createEl(
`get ${key}`,
data,
descriptor.get,
type,
firstLevel
)
}
if (hasSetter) {
ret += this._createEl(
`set ${key}`,
data,
descriptor.set,
type,
firstLevel
)
}
}
})
const proto = getProto(data)
if (!firstLevel && proto) {
if (ret === '') {
const id = visitor.set(proto, {
self: data
})
this._map[id] = proto
ret = this._objToHtml(proto)
} else {
ret += this._createEl('__proto__', self || data, proto, 'proto')
}
}
return ret
}
_createEl(key, self, val, keyType, firstLevel = false) {
const visitor = this._visitor
let t = typeof val
let valType = type(val, false)
if (keyType === 'virtual') valType = key
if (val === null) {
return `<li>${wrapKey(key)}<span class="eruda-null">null</span></li>`
} else if (isNum(val) || isBool(val)) {
return `<li>${wrapKey(key)}<span class="eruda-${t}">${encode(
val
)}</span></li>`
}
if (valType === 'RegExp') t = 'regexp'
if (valType === 'Number') t = 'number'
if (valType === 'Number' || valType === 'RegExp') {
return `<li>${wrapKey(key)}<span class="eruda-${t}">${encode(
val.value
)}</span></li>`
} else if (valType === 'Undefined' || valType === 'Symbol') {
return `<li>${wrapKey(key)}<span class="eruda-special">${lowerCase(
valType
)}</span></li>`
} else if (val === '(...)') {
return `<li>${wrapKey(key)}<span class="eruda-special">${val}</span></li>`
} else if (isObj(val)) {
const visitedObj = visitor.get(val)
let id
if (visitedObj) {
id = visitedObj.id
} else {
const extra = {}
if (keyType === 'proto') {
extra.self = self
}
id = visitor.set(val, extra)
this._map[id] = val
}
const objAbstract = getObjAbstract(val, valType) || upperFirst(t)
let obj = `<li ${
firstLevel ? 'data-first-level="true"' : ''
} ${'data-object-id="' + id + '"'}><span class="${
firstLevel ? '' : 'eruda-expanded eruda-collapsed'
}"></span>${wrapKey(key)}<span class="eruda-open">${
firstLevel ? '' : objAbstract
}</span><ul class="eruda-${t}" ${
firstLevel ? '' : 'style="display:none"'
}>`
if (firstLevel) obj += this._objToHtml(val)
return obj + '</ul><span class="eruda-close"></span></li>'
}
function wrapKey(key) {
if (firstLevel) return ''
if (isObj(val) && keyType === 'virtual') return ''
let keyClass = 'eruda-key'
if (
keyType === 'unenumerable' ||
keyType === 'proto' ||
keyType === 'symbol'
) {
keyClass = 'eruda-key-lighter'
}
return `<span class="${keyClass}">${encode(key)}</span>: `
}
return `<li>${wrapKey(key)}<span class="eruda-${typeof val}">"${encode(
val
)}"</span></li>`
}
_appendTpl() {
this._$el.html(this._objToHtml(this._data, true))
}
_bindEvent() {
const map = this._map
const self = this
this._$el.on('click', 'li', function(e) {
const $this = $(this)
const circularId = $this.data('object-id')
const $firstSpan = $(this)
.find('span')
.eq(0)
if ($this.data('first-level')) return
if (circularId) {
$this.find('ul').html(self._objToHtml(map[circularId], false))
$this.rmAttr('data-object-id')
}
e.stopImmediatePropagation()
if (!$firstSpan.hasClass('eruda-expanded')) return
const $ul = $this.find('ul').eq(0)
if ($firstSpan.hasClass('eruda-collapsed')) {
$firstSpan.rmClass('eruda-collapsed')
$ul.show()
} else {
$firstSpan.addClass('eruda-collapsed')
$ul.hide()
}
self.emit('change')
})
}
}
function getObjAbstract(data, type) {
if (!type) return
if (type === 'Function') {
return getFnAbstract(toSrc(data))
}
if (type === 'Array') {
return `Array(${data.length})`
}
return type
}
class Visitor {
constructor() {
this.id = 0
this.visited = []
}
set(val, extra) {
const { visited, id } = this
const obj = {
id,
val
}
extend(obj, extra)
visited.push(obj)
this.id++
return id
}
get(val) {
const { visited } = this
for (let i = 0, len = visited.length; i < len; i++) {
const obj = visited[i]
if (val === obj.val) return obj
}
return false
}
}

View File

@@ -74,10 +74,7 @@ function resetStyle({ css, el }) {
css = css.replace(/(\d+)px/g, ($0, $1) => +$1 * scale + 'px')
css = css.replace(/_/g, 'eruda-')
each(cssMap, (val, key) => {
css = css.replace(
new RegExp(escapeRegExp(`$${val}:`), 'g'),
key + ':'
)
css = css.replace(new RegExp(escapeRegExp(`$${val}:`), 'g'), key + ':')
})
const _keys = keys(themes.Light)
each(_keys, key => {

View File

@@ -1,84 +0,0 @@
@import '../style/variable';
@import '../style/mixin';
.container .json {
@include overflow-auto(x);
cursor: default;
font-size: $font-size-s;
line-height: 1.2;
min-height: 100%;
color: var(--primary);
&,
ul {
list-style: none !important;
}
ul {
padding: 0 !important;
padding-left: 15px !important;
margin: 0 !important;
}
li {
position: relative;
white-space: nowrap;
line-height: 16px;
min-height: 16px;
}
& > li > .key {
display: none;
}
& > li {
padding: $padding 0;
}
.array .object .key {
display: inline;
}
.null {
color: var(--operator-color);
}
.string,
.regexp {
color: var(--string-color);
}
.number {
color: var(--number-color);
}
.boolean {
color: var(--keyword-color);
}
.special {
color: var(--operator-color);
}
.key,
.key-lighter {
color: var(--var-color);
}
.key-lighter {
opacity: 0.6;
}
.expanded:before {
content: '';
width: 0;
height: 0;
border: 4px solid transparent;
position: absolute;
border-top-color: var(--foreground);
left: -12px;
top: 6px;
}
.collapsed:before {
content: '';
border-left-color: var(--foreground);
border-top-color: transparent;
left: -10px;
top: 4px;
}
li .collapsed ~ .close:before {
color: #999;
}
.hidden ~ ul {
display: none;
}
span {
position: static !important;
}
}

37
src/style/luna.scss Normal file
View File

@@ -0,0 +1,37 @@
@import './variable';
.luna-object-viewer {
color: var(--primary);
font-size: 12px !important;
& > li {
padding: $padding 0 !important;
}
}
.luna-object-viewer-null {
color: var(--operator-color);
}
.luna-object-viewer-string,
.luna-object-viewer-regexp {
color: var(--string-color);
}
.luna-object-viewer-number {
color: var(--number-color);
}
.luna-object-viewer-boolean {
color: var(--keyword-color);
}
.luna-object-viewer-special {
color: var(--operator-color);
}
.luna-object-viewer-key,
.luna-object-viewer-key-lighter {
color: var(--var-color);
}
.luna-object-viewer-expanded:before {
border-color: transparent;
border-top-color: var(--foreground);
}
.luna-object-viewer-collapsed:before {
border-top-color: transparent;
border-left-color: var(--foreground);
}

View File

@@ -1,5 +1,6 @@
@import 'variable';
@import 'mixin';
@import 'luna';
.container {
pointer-events: none;