eruda3 / src /Console /Console.js
soiz1's picture
Update src/Console/Console.js
a9acea5 verified
import Tool from '../DevTools/Tool'
import noop from 'licia/noop'
import $ from 'licia/$'
import toStr from 'licia/toStr'
import isFn from 'licia/isFn'
import Emitter from 'licia/Emitter'
import isStr from 'licia/isStr'
import isRegExp from 'licia/isRegExp'
import uncaught from 'licia/uncaught'
import trim from 'licia/trim'
import upperFirst from 'licia/upperFirst'
import isHidden from 'licia/isHidden'
import isNull from 'licia/isNull'
import isArr from 'licia/isArr'
import extend from 'licia/extend'
import evalCss from '../lib/evalCss'
import Settings from '../Settings/Settings'
import LunaConsole from 'luna-console'
import LunaModal from 'luna-modal'
import { classPrefix as c } from '../lib/util'
uncaught.start()
export default class Console extends Tool {
constructor({ name = 'console' } = {}) {
super()
Emitter.mixin(this)
this.displayName = 'コンソール'
this.name = name
this._selectedLog = null
}
init($el, container) {
super.init($el)
this._container = container
this._appendTpl()
this._initCfg()
this._initLogger()
this._exposeLogger()
this._bindEvent()
}
show() {
super.show()
this._handleShow()
}
overrideConsole() {
const origConsole = (this._origConsole = {})
const winConsole = window.console
CONSOLE_METHOD.forEach((name) => {
let origin = (origConsole[name] = noop)
if (winConsole[name]) {
origin = origConsole[name] = winConsole[name].bind(winConsole)
}
winConsole[name] = (...args) => {
this[name](...args)
origin(...args)
}
})
return this
}
setGlobal(name, val) {
this._logger.setGlobal(name, val)
}
restoreConsole() {
if (!this._origConsole) return this
CONSOLE_METHOD.forEach(
(name) => (window.console[name] = this._origConsole[name])
)
delete this._origConsole
return this
}
catchGlobalErr() {
uncaught.addListener(this._handleErr)
return this
}
ignoreGlobalErr() {
uncaught.rmListener(this._handleErr)
return this
}
filter(filter) {
const $filterText = this._$filterText
const logger = this._logger
if (isStr(filter)) {
$filterText.text(filter)
logger.setOption('filter', trim(filter))
} else if (isRegExp(filter)) {
$filterText.text(toStr(filter))
logger.setOption('filter', filter)
} else if (isFn(filter)) {
$filterText.text('ƒ')
logger.setOption('filter', filter)
}
}
destroy() {
this._logger.destroy()
super.destroy()
this._container.off('show', this._handleShow)
if (this._style) {
evalCss.remove(this._style)
}
this.ignoreGlobalErr()
this.restoreConsole()
this._rmCfg()
}
_handleShow = () => {
if (isHidden(this._$el.get(0))) return
this._logger.renderViewport()
}
_handleErr = (err) => {
this._logger.error(err)
}
_enableJsExecution(enabled) {
const $el = this._$el
const $jsInput = $el.find(c('.js-input'))
if (enabled) {
$jsInput.show()
$el.rmClass(c('js-input-hidden'))
} else {
$jsInput.hide()
$el.addClass(c('js-input-hidden'))
}
}
_appendTpl() {
const $el = this._$el
this._style = evalCss(require('./Console.scss'))
$el.append(
c(`
<div class="control">
<span class="icon-clear clear-console"></span>
<span class="level active" data-level="all">全て</span>
<span class="level" data-level="info">情報</span>
<span class="level" data-level="warning">警告</span>
<span class="level" data-level="error">エラー</span>
<span class="filter-text"></span>
<span class="icon-filter filter"></span>
<span class="icon-copy icon-disabled copy"></span>
</div>
<div class="logs-container"></div>
<div class="js-input">
<div class="buttons">
<div class="button cancel">キャンセル</div>
<div class="button execute">実行</div>
</div>
<span class="icon-arrow-right"></span>
<textarea></textarea>
</div>
`)
)
const _$inputContainer = $el.find(c('.js-input'))
const _$input = _$inputContainer.find('textarea')
const _$inputBtns = _$inputContainer.find(c('.buttons'))
extend(this, {
_$control: $el.find(c('.control')),
_$logs: $el.find(c('.logs-container')),
_$inputContainer,
_$input,
_$inputBtns,
_$filterText: $el.find(c('.filter-text')),
})
}
_initLogger() {
const cfg = this.config
let maxLogNum = cfg.get('maxLogNum')
maxLogNum = maxLogNum === 'infinite' ? 0 : +maxLogNum
const $level = this._$control.find(c('.level'))
const logger = new LunaConsole(this._$logs.get(0), {
asyncRender: cfg.get('asyncRender'),
maxNum: maxLogNum,
showHeader: cfg.get('displayExtraInfo'),
unenumerable: cfg.get('displayUnenumerable'),
accessGetter: cfg.get('displayGetterVal'),
lazyEvaluation: cfg.get('lazyEvaluation'),
})
logger.on('optionChange', (name, val) => {
switch (name) {
case 'level':
$level.each(function () {
const $this = $(this)
const level = $this.data('level')
let isMatch = false
if (level === 'all') {
isMatch = isArr(val) && val.length === 4 // ['verbose','info','warning','error']
} else {
isMatch = level === val
}
$this.toggleClass(c('active'), isMatch)
})
break
}
})
if (cfg.get('overrideConsole')) this.overrideConsole()
this._logger = logger
}
_exposeLogger() {
const logger = this._logger
const methods = ['html'].concat(CONSOLE_METHOD)
methods.forEach(
(name) =>
(this[name] = (...args) => {
logger[name](...args)
this.emit(name, ...args)
return this
})
)
}
_bindEvent() {
const container = this._container
const $input = this._$input
const $inputBtns = this._$inputBtns
const $control = this._$control
const logger = this._logger
const config = this.config
$control
.on('click', c('.clear-console'), () => logger.clear(true))
.on('click', c('.level'), function () {
const $this = $(this)
const level = $this.data('level')
let filterLevel
// すべてのレベルボタンからactiveクラスを削除
$control.find(c('.level')).rmClass(c('active'))
if (level === 'all') {
filterLevel = ['verbose', 'info', 'warning', 'error']
} else {
filterLevel = level
}
// クリックされたボタンにactiveクラスを追加
$this.addClass(c('active'))
logger.setOption('level', filterLevel)
})
.on('click', c('.filter'), () => {
LunaModal.prompt('Filter').then((filter) => {
if (isNull(filter)) return
this.filter(filter)
})
})
.on('click', c('.copy'), () => {
this._selectedLog.copy()
container.notify('Copied', { icon: 'success' })
})
$inputBtns
.on('click', c('.cancel'), () => this._hideInput())
.on('click', c('.execute'), () => {
const jsInput = $input.val().trim()
if (jsInput === '') return
logger.evaluate(jsInput)
$input.val('').get(0).blur()
this._hideInput()
})
$input.on('focusin', () => this._showInput())
logger.on('insert', (log) => {
const autoShow = log.type === 'error' && config.get('displayIfErr')
if (autoShow) container.showTool('console').show()
})
logger.on('select', (log) => {
this._selectedLog = log
$control.find(c('.icon-copy')).rmClass(c('icon-disabled'))
})
logger.on('deselect', () => {
this._selectedLog = null
$control.find(c('.icon-copy')).addClass(c('icon-disabled'))
})
container.on('show', this._handleShow)
}
_hideInput() {
this._$inputContainer.rmClass(c('active'))
this._$inputBtns.css('display', 'none')
}
_showInput() {
this._$inputContainer.addClass(c('active'))
this._$inputBtns.css('display', 'flex')
}
_rmCfg() {
const cfg = this.config
const settings = this._container.get('settings')
if (!settings) return
settings
.remove(cfg, 'asyncRender')
.remove(cfg, 'jsExecution')
.remove(cfg, 'catchGlobalErr')
.remove(cfg, 'overrideConsole')
.remove(cfg, 'displayExtraInfo')
.remove(cfg, 'displayUnenumerable')
.remove(cfg, 'displayGetterVal')
.remove(cfg, 'lazyEvaluation')
.remove(cfg, 'displayIfErr')
.remove(cfg, 'maxLogNum')
.remove(upperFirst(this.name))
}
_initCfg() {
const container = this._container
const cfg = (this.config = Settings.createCfg(this.name, {
asyncRender: true,
catchGlobalErr: true,
jsExecution: true,
overrideConsole: true,
displayExtraInfo: false,
displayUnenumerable: true,
displayGetterVal: true,
lazyEvaluation: true,
displayIfErr: false,
maxLogNum: 'infinite',
}))
this._enableJsExecution(cfg.get('jsExecution'))
if (cfg.get('catchGlobalErr')) this.catchGlobalErr()
cfg.on('change', (key, val) => {
const logger = this._logger
switch (key) {
case 'asyncRender':
return logger.setOption('asyncRender', val)
case 'jsExecution':
return this._enableJsExecution(val)
case 'catchGlobalErr':
return val ? this.catchGlobalErr() : this.ignoreGlobalErr()
case 'overrideConsole':
return val ? this.overrideConsole() : this.restoreConsole()
case 'maxLogNum':
return logger.setOption('maxNum', val === 'infinite' ? 0 : +val)
case 'displayExtraInfo':
return logger.setOption('showHeader', val)
case 'displayUnenumerable':
return logger.setOption('unenumerable', val)
case 'displayGetterVal':
return logger.setOption('accessGetter', val)
case 'lazyEvaluation':
return logger.setOption('lazyEvaluation', val)
}
})
const settings = container.get('settings')
if (!settings) return
settings
.text(upperFirst(this.displayName))
.switch(cfg, 'asyncRender', '非同期レンダリング')
.switch(cfg, 'jsExecution', 'JavaScriptの実行を有効にする')
.switch(cfg, 'catchGlobalErr', 'グローバルエラーをキャッチする')
.switch(cfg, 'overrideConsole', 'コンソールを上書きする')
.switch(cfg, 'displayIfErr', 'エラーが発生した場合に自動表示')
.switch(cfg, 'displayExtraInfo', '追加情報を表示する')
.switch(cfg, 'displayUnenumerable', '列挙できないプロパティを表示する')
.switch(cfg, 'displayGetterVal', 'ゲッター値にアクセスする')
.switch(cfg, 'lazyEvaluation', '遅延評価')
.select(cfg, 'maxLogNum', '最大ログ数', [
'infinite',
'250',
'125',
'100',
'50',
'10',
])
.separator()
}
}
const CONSOLE_METHOD = [
'log',
'error',
'info',
'warn',
'dir',
'time',
'timeLog',
'timeEnd',
'clear',
'table',
'assert',
'count',
'countReset',
'debug',
'group',
'groupCollapsed',
'groupEnd',
]