|
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 |
|
} 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 |
|
|
|
|
|
$control.find(c('.level')).rmClass(c('active')) |
|
|
|
if (level === 'all') { |
|
filterLevel = ['verbose', 'info', 'warning', 'error'] |
|
} else { |
|
filterLevel = level |
|
} |
|
|
|
|
|
$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', |
|
] |