diff --git a/src/Console/Log.js b/src/Console/Log.js
index 2f34292..a81efc8 100644
--- a/src/Console/Log.js
+++ b/src/Console/Log.js
@@ -2,6 +2,7 @@ import stringify from './stringify'
import origGetAbstract from '../lib/getAbstract'
import beautify from 'js-beautify'
import JsonViewer from '../lib/JsonViewer'
+import ObjViewer from '../lib/ObjViewer'
import {
isObj,
isStr,
@@ -185,7 +186,8 @@ export default class Log extends Emitter {
}
}
click(logger) {
- const { type, src, args } = this
+ const { type, src } = this
+ let { args } = this
const $el = this._$el
switch (type) {
@@ -198,23 +200,29 @@ export default class Log extends Emitter {
case 'dir':
case 'group':
case 'groupCollapsed':
- if (src) {
+ if (src || args) {
const $json = $el.find('.eruda-json')
if ($json.hasClass('eruda-hidden')) {
if ($json.data('init') !== 'true') {
- const jsonViewer = new JsonViewer(src, $json)
- jsonViewer.on('change', () => this.updateSize(false))
+ if (src) {
+ const jsonViewer = new JsonViewer(src, $json)
+ jsonViewer.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
+ })
+ objViewer.on('change', () => this.updateSize(false))
+ }
$json.data('init', 'true')
}
$json.rmClass('eruda-hidden')
} else {
$json.addClass('eruda-hidden')
}
- } else if (args) {
- this.extractObj(() => {
- this.click(logger)
- delete this.args
- })
} else if (type === 'group' || type === 'groupCollapsed') {
logger.toggleGroup(this)
}
diff --git a/src/Network/Network.scss b/src/Network/Network.scss
index fe7939d..fcb4444 100644
--- a/src/Network/Network.scss
+++ b/src/Network/Network.scss
@@ -121,7 +121,8 @@
bottom: 0;
color: var(--foreground);
width: 100%;
- background: var(--accent);
+ border-top: 1px solid var(--border);
+ background: var(--darker-background);
display: block;
height: 40px;
line-height: 40px;
diff --git a/src/lib/JsonViewer.js b/src/lib/JsonViewer.js
index 1e3e43f..e4d26cb 100644
--- a/src/lib/JsonViewer.js
+++ b/src/lib/JsonViewer.js
@@ -53,7 +53,7 @@ export default class JsonViewer extends Emitter {
this._appendTpl()
this._bindEvent()
}
- jsonToHtml(data, firstLevel) {
+ _jsonToHtml(data, firstLevel) {
let ret = ''
each(['enumerable', 'unenumerable', 'symbol'], type => {
@@ -63,23 +63,22 @@ export default class JsonViewer extends Emitter {
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)
+ ret += this._createEl(key, data[type][key], type, firstLevel)
}
})
if (data.proto) {
if (ret === '') {
- ret = this.jsonToHtml(data.proto, firstLevel)
+ ret = this._jsonToHtml(data.proto)
} else {
- ret += this.createEl('__proto__', data.proto, 'proto', firstLevel)
+ ret += this._createEl('__proto__', data.proto, 'proto')
}
}
return ret
}
- createEl(key, val, keyType, firstLevel = false) {
+ _createEl(key, val, keyType, firstLevel = false) {
let type = typeof val
- let id
if (val === null) {
return `
${wrapKey(key)}null`
@@ -103,7 +102,7 @@ export default class JsonViewer extends Emitter {
} else if (val === '(...)') {
return `${wrapKey(key)}${val}`
} else if (isObj(val)) {
- id = val.id
+ const id = val.id
const referenceId = val.reference
const objAbstract = getObjAbstract(val) || upperFirst(type)
@@ -121,7 +120,7 @@ export default class JsonViewer extends Emitter {
firstLevel ? '' : 'style="display:none"'
}>`
- if (firstLevel) obj += this.jsonToHtml(this._map[id])
+ if (firstLevel) obj += this._jsonToHtml(this._map[id])
return obj + ''
}
@@ -149,7 +148,7 @@ export default class JsonViewer extends Emitter {
_appendTpl() {
const data = this._map[this._data.id]
- this._$el.html(this.jsonToHtml(data, true))
+ this._$el.html(this._jsonToHtml(data, true))
}
_bindEvent() {
const map = this._map
@@ -165,7 +164,7 @@ export default class JsonViewer extends Emitter {
if ($this.data('first-level')) return
if (circularId) {
- $this.find('ul').html(self.jsonToHtml(map[circularId], false))
+ $this.find('ul').html(self._jsonToHtml(map[circularId], false))
$this.rmAttr('data-object-id')
}
@@ -268,14 +267,14 @@ function objToArr(data, id, type) {
return ret
}
-const encode = str => {
+export const encode = str => {
return escape(toStr(str))
.replace(/\n/g, '↵')
.replace(/\f|\r|\t/g, '')
}
// $, upperCase, lowerCase, _
-function sortObjName(a, b) {
+export function sortObjName(a, b) {
const numA = toNum(a)
const numB = toNum(b)
if (!isNaN(numA) && !isNaN(numB)) {
@@ -320,7 +319,7 @@ function transCode(code) {
return code
}
-function getObjAbstract(data) {
+export function getObjAbstract(data) {
const { type, value } = data
if (!type) return
@@ -344,7 +343,7 @@ function extractFnHead(str) {
return str
}
-function getFnAbstract(str) {
+export function getFnAbstract(str) {
if (str.length > 500) str = str.slice(0, 500) + '...'
return 'ƒ ' + trim(extractFnHead(str).replace('function', ''))
diff --git a/src/lib/ObjViewer.js b/src/lib/ObjViewer.js
new file mode 100644
index 0000000..a273480
--- /dev/null
+++ b/src/lib/ObjViewer.js
@@ -0,0 +1,267 @@
+import {
+ Emitter,
+ getProto,
+ isNum,
+ isBool,
+ lowerCase,
+ isObj,
+ upperFirst,
+ keys,
+ each,
+ toSrc,
+ isPromise,
+ type,
+ $,
+ difference,
+ allKeys,
+ filter
+} 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) {
+ let ret = ''
+
+ const types = ['enumerable']
+ const enumerableKeys = keys(data)
+ let unenumerableKeys = []
+ let symbolKeys = []
+
+ 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'
+ }
+ )
+ }
+
+ each(['enumerable', 'unenumerable', 'symbol'], type => {
+ let typeKeys = []
+ if (type === 'symbol') {
+ typeKeys = symbolKeys
+ } else if (type === 'unenumerable') {
+ typeKeys = unenumerableKeys
+ } else {
+ typeKeys = enumerableKeys
+ }
+ typeKeys.sort(sortObjName)
+ for (let i = 0, len = typeKeys.length; i < len; i++) {
+ const key = typeKeys[i]
+ let val = ''
+ try {
+ val = data[key]
+ if (isPromise(val)) {
+ val.catch(() => {})
+ }
+ } catch (e) {
+ val = e.message
+ }
+ ret += this._createEl(key, val, type, firstLevel)
+ }
+ })
+
+ const proto = getProto(data)
+ if (!firstLevel && proto) {
+ if (ret === '') {
+ ret = this._objToHtml(proto)
+ } else {
+ ret += this._createEl('__proto__', proto, 'proto')
+ }
+ }
+
+ return ret
+ }
+ _createEl(key, val, keyType, firstLevel = false) {
+ const visitor = this._visitor
+ let t = typeof val
+ const valType = type(val, false)
+
+ if (val === null) {
+ return `${wrapKey(key)}null`
+ } else if (isNum(val) || isBool(val)) {
+ return `${wrapKey(key)}${encode(
+ val
+ )}`
+ }
+
+ if (valType === 'RegExp') t = 'regexp'
+ if (valType === 'Number') t = 'number'
+
+ if (valType === 'Number' || valType === 'RegExp') {
+ return `${wrapKey(key)}${encode(
+ val.value
+ )}`
+ } else if (valType === 'Undefined' || valType === 'Symbol') {
+ return `${wrapKey(key)}${lowerCase(
+ valType
+ )}`
+ } else if (val === '(...)') {
+ return `${wrapKey(key)}${val}`
+ } else if (isObj(val)) {
+ const visitedObj = visitor.get(val)
+ let id
+ if (visitedObj) {
+ id = visitedObj.id
+ } else {
+ id = visitor.set(val)
+ this._map[id] = val
+ }
+ const objAbstract = getObjAbstract(val, valType) || upperFirst(t)
+
+ let obj = `
+
+ ${wrapKey(key)}
+ ${
+ firstLevel ? '' : objAbstract
+ }
+ `
+
+ if (firstLevel) obj += this._objToHtml(val)
+
+ return obj + '
'
+ }
+
+ 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 `${encode(key)}: `
+ }
+
+ return `${wrapKey(key)}"${encode(
+ val
+ )}"`
+ }
+ _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) {
+ const { visited, id } = this
+ const obj = {
+ id,
+ val
+ }
+ 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
+ }
+}
diff --git a/src/lib/getAbstract.js b/src/lib/getAbstract.js
index b782351..98bebae 100644
--- a/src/lib/getAbstract.js
+++ b/src/lib/getAbstract.js
@@ -8,9 +8,9 @@ import {
each,
getObjType,
endWith,
- isEmpty,
- evalCss
+ isEmpty
} from './util'
+import evalCss from './evalCss'
let hasEvalCss = false
diff --git a/src/lib/themes.js b/src/lib/themes.js
index 7669be6..7ebc11d 100644
--- a/src/lib/themes.js
+++ b/src/lib/themes.js
@@ -13,7 +13,7 @@ export default {
contrast: '#f2f7fd',
varColor: '#c80000',
stringColor: '#1a1aa6',
- keywordColor: '#0d22aa',
+ keywordColor: '#881280',
numberColor: '#1c00cf',
operatorColor: '#808080',
linkColor: '#1155cc',