|
import logger from '../lib/logger' |
|
import Tool from './Tool' |
|
import Settings from '../Settings/Settings' |
|
import Emitter from 'licia/Emitter' |
|
import defaults from 'licia/defaults' |
|
import keys from 'licia/keys' |
|
import last from 'licia/last' |
|
import each from 'licia/each' |
|
import isNum from 'licia/isNum' |
|
import nextTick from 'licia/nextTick' |
|
import $ from 'licia/$' |
|
import toNum from 'licia/toNum' |
|
import extend from 'licia/extend' |
|
import isStr from 'licia/isStr' |
|
import theme from 'licia/theme' |
|
import upperFirst from 'licia/upperFirst' |
|
import startWith from 'licia/startWith' |
|
import ready from 'licia/ready' |
|
import pointerEvent from 'licia/pointerEvent' |
|
import evalCss from '../lib/evalCss' |
|
import emitter from '../lib/emitter' |
|
import { isDarkTheme } from '../lib/themes' |
|
import LunaNotification from 'luna-notification' |
|
import LunaModal from 'luna-modal' |
|
import LunaTab from 'luna-tab' |
|
import { |
|
classPrefix as c, |
|
eventClient, |
|
hasSafeArea, |
|
safeStorage, |
|
} from '../lib/util' |
|
|
|
export default class DevTools extends Emitter { |
|
constructor($container, { defaults = {}, inline = false } = {}) { |
|
super() |
|
|
|
this._defCfg = extend( |
|
{ |
|
transparency: 1, |
|
displaySize: 80, |
|
theme: 'System preference', |
|
}, |
|
defaults |
|
) |
|
|
|
this._style = evalCss(require('./DevTools.scss')) |
|
|
|
this.$container = $container |
|
this._isShow = false |
|
this._opacity = 1 |
|
this._tools = {} |
|
this._isResizing = false |
|
this._resizeTimer = null |
|
this._resizeStartY = 0 |
|
this._resizeStartSize = 0 |
|
this._inline = inline |
|
|
|
this._initTpl() |
|
this._initTab() |
|
this._initNotification() |
|
this._initModal() |
|
|
|
ready(() => this._checkSafeArea()) |
|
this._bindEvent() |
|
} |
|
show() { |
|
this._isShow = true |
|
|
|
this._$el.show() |
|
this._tab.updateSlider() |
|
|
|
|
|
setTimeout(() => { |
|
this._$el.css('opacity', this._opacity) |
|
}, 50) |
|
|
|
this.emit('show') |
|
|
|
return this |
|
} |
|
hide() { |
|
if (this._inline) { |
|
return |
|
} |
|
|
|
this._isShow = false |
|
this.emit('hide') |
|
|
|
this._$el.css({ opacity: 0 }) |
|
setTimeout(() => this._$el.hide(), 300) |
|
|
|
return this |
|
} |
|
toggle() { |
|
return this._isShow ? this.hide() : this.show() |
|
} |
|
add(tool) { |
|
const tab = this._tab |
|
|
|
if (!(tool instanceof Tool)) { |
|
const { init, show, hide, destroy } = new Tool() |
|
defaults(tool, { init, show, hide, destroy }) |
|
} |
|
|
|
const name = tool.name |
|
if (!name) { |
|
return logger.error('You must specify a name for a tool') |
|
} |
|
|
|
if (this._tools[name]) { |
|
return logger.warn(`Tool ${name} already exists`) |
|
} |
|
|
|
const id = name.replace(/\s+/g, '-') |
|
this._$tools.prepend(`<div id="${c(id)}" class="${c(id + ' tool')}"></div>`) |
|
tool.init(this._$tools.find(`.${c(id)}.${c('tool')}`), this) |
|
tool.active = false |
|
this._tools[name] = tool |
|
|
|
if (name === 'settings') { |
|
tab.append({ |
|
id: name, |
|
title: tool.displayName || name, |
|
}) |
|
} else { |
|
tab.insert(tab.length - 1, { |
|
id: name, |
|
title: tool.displayName || name, |
|
}) |
|
} |
|
|
|
return this |
|
} |
|
remove(name) { |
|
const tools = this._tools |
|
|
|
if (!tools[name]) return logger.warn(`Tool ${name} doesn't exist`) |
|
|
|
this._tab.remove(name) |
|
|
|
const tool = tools[name] |
|
delete tools[name] |
|
if (tool.active) { |
|
const toolKeys = keys(tools) |
|
if (toolKeys.length > 0) this.showTool(tools[last(toolKeys)].name) |
|
} |
|
tool.destroy() |
|
|
|
return this |
|
} |
|
removeAll() { |
|
each(this._tools, (tool) => this.remove(tool.name)) |
|
|
|
return this |
|
} |
|
get(name) { |
|
const tool = this._tools[name] |
|
|
|
if (tool) return tool |
|
} |
|
showTool(name) { |
|
if (this._curTool === name) { |
|
return this |
|
} |
|
this._curTool = name |
|
|
|
const tools = this._tools |
|
|
|
const tool = tools[name] |
|
if (!tool) return |
|
|
|
let lastTool = {} |
|
|
|
each(tools, (tool) => { |
|
if (tool.active) { |
|
lastTool = tool |
|
tool.active = false |
|
tool.hide() |
|
} |
|
}) |
|
|
|
tool.active = true |
|
tool.show() |
|
|
|
this._tab.select(name) |
|
|
|
this.emit('showTool', name, lastTool) |
|
|
|
return this |
|
} |
|
initCfg(settings) { |
|
const cfg = (this.config = Settings.createCfg('dev-tools', this._defCfg)) |
|
|
|
this._setTransparency(cfg.get('transparency')) |
|
this._setDisplaySize(cfg.get('displaySize')) |
|
this._setTheme(cfg.get('theme')) |
|
|
|
cfg.on('change', (key, val) => { |
|
switch (key) { |
|
case 'transparency': |
|
return this._setTransparency(val) |
|
case 'displaySize': |
|
return this._setDisplaySize(val) |
|
case 'theme': |
|
return this._setTheme(val) |
|
} |
|
}) |
|
|
|
settings |
|
.separator() |
|
.select(cfg, 'theme', 'テーマ', [ |
|
'システム設定に従う', |
|
...keys(evalCss.getThemes()), |
|
]) |
|
|
|
if (!this._inline) { |
|
settings |
|
.range(cfg, 'transparency', '透明度', { |
|
min: 0.2, |
|
max: 1, |
|
step: 0.01, |
|
}) |
|
.range(cfg, 'displaySize', '表示サイズ', { |
|
min: 40, |
|
max: 100, |
|
step: 1, |
|
}) |
|
} |
|
|
|
settings |
|
.button('デフォルトに戻して再読み込み', function () { |
|
const store = safeStorage('local') |
|
|
|
const data = JSON.parse(JSON.stringify(store)) |
|
each(data, (val, key) => { |
|
if (!isStr(val)) { |
|
return |
|
} |
|
|
|
if (startWith(key, 'eruda')) { |
|
store.removeItem(key) |
|
} |
|
}) |
|
|
|
window.location.reload() |
|
}) |
|
.separator() |
|
} |
|
notify(content, options) { |
|
this._notification.notify(content, options) |
|
} |
|
destroy() { |
|
evalCss.remove(this._style) |
|
this.removeAll() |
|
this._tab.destroy() |
|
this._$el.remove() |
|
window.removeEventListener('resize', this._checkSafeArea) |
|
emitter.off(emitter.SCALE, this._updateTabHeight) |
|
} |
|
_checkSafeArea = () => { |
|
const { $container } = this |
|
|
|
if (hasSafeArea()) { |
|
$container.addClass(c('safe-area')) |
|
} else { |
|
$container.rmClass(c('safe-area')) |
|
} |
|
} |
|
_setTheme(t) { |
|
const { $container } = this |
|
|
|
if (t === 'System preference') { |
|
t = upperFirst(theme.get()) |
|
} |
|
|
|
if (isDarkTheme(t)) { |
|
$container.addClass(c('dark')) |
|
} else { |
|
$container.rmClass(c('dark')) |
|
} |
|
evalCss.setTheme(t) |
|
} |
|
_setTransparency(opacity) { |
|
if (!isNum(opacity)) return |
|
|
|
this._opacity = opacity |
|
if (this._isShow) this._$el.css({ opacity }) |
|
} |
|
_setDisplaySize(height) { |
|
if (this._inline) { |
|
height = 100 |
|
} |
|
|
|
if (!isNum(height)) return |
|
|
|
this._$el.css({ height: height + '%' }) |
|
} |
|
_initTpl() { |
|
const $container = this.$container |
|
|
|
$container.append( |
|
c(` |
|
<div class="dev-tools"> |
|
<div class="resizer"></div> |
|
<div class="tab"></div> |
|
<div class="tools"></div> |
|
<div class="notification"></div> |
|
<div class="modal"></div> |
|
</div> |
|
`) |
|
) |
|
|
|
this._$el = $container.find(c('.dev-tools')) |
|
this._$tools = this._$el.find(c('.tools')) |
|
} |
|
_initTab() { |
|
this._tab = new LunaTab(this._$el.find(c('.tab')).get(0), { |
|
height: 40, |
|
}) |
|
this._tab.on('select', (id) => this.showTool(id)) |
|
} |
|
_updateTabHeight = (scale) => { |
|
this._tab.setOption('height', 40 * scale) |
|
nextTick(() => { |
|
this._tab.updateSlider() |
|
}) |
|
} |
|
_initNotification() { |
|
this._notification = new LunaNotification( |
|
this._$el.find(c('.notification')).get(0), |
|
{ |
|
position: { |
|
x: 'center', |
|
y: 'top', |
|
}, |
|
} |
|
) |
|
} |
|
_initModal() { |
|
LunaModal.setContainer(this._$el.find(c('.modal')).get(0)) |
|
} |
|
_bindEvent() { |
|
const $resizer = this._$el.find(c('.resizer')) |
|
const $navBar = this._$el.find(c('.nav-bar')) |
|
const $document = $(document) |
|
|
|
if (this._inline) { |
|
$resizer.hide() |
|
} |
|
|
|
const startListener = (e) => { |
|
e.preventDefault() |
|
e.stopPropagation() |
|
|
|
e = e.origEvent |
|
this._isResizing = true |
|
this._resizeStartSize = this.config.get('displaySize') |
|
this._resizeStartY = eventClient('y', e) |
|
|
|
$resizer.css('height', '100%') |
|
|
|
$document.on(pointerEvent('move'), moveListener) |
|
$document.on(pointerEvent('up'), endListener) |
|
} |
|
const moveListener = (e) => { |
|
if (!this._isResizing) { |
|
return |
|
} |
|
e.preventDefault() |
|
e.stopPropagation() |
|
|
|
e = e.origEvent |
|
const deltaY = |
|
((this._resizeStartY - eventClient('y', e)) / window.innerHeight) * 100 |
|
let displaySize = this._resizeStartSize + deltaY |
|
if (displaySize < 40) { |
|
displaySize = 40 |
|
} else if (displaySize > 100) { |
|
displaySize = 100 |
|
} |
|
this.config.set('displaySize', toNum(displaySize.toFixed(2))) |
|
} |
|
const endListener = () => { |
|
clearTimeout(this._resizeTimer) |
|
this._isResizing = false |
|
|
|
$resizer.css('height', 10) |
|
|
|
$document.off(pointerEvent('move'), moveListener) |
|
$document.off(pointerEvent('up'), endListener) |
|
} |
|
$resizer.css('height', 10) |
|
$resizer.on(pointerEvent('down'), startListener) |
|
|
|
$navBar.on('contextmenu', (e) => e.preventDefault()) |
|
this.$container.on('click', (e) => e.stopPropagation()) |
|
window.addEventListener('resize', this._checkSafeArea) |
|
|
|
emitter.on(emitter.SCALE, this._updateTabHeight) |
|
|
|
theme.on('change', () => { |
|
const t = this.config.get('theme') |
|
if (t === 'System preference') { |
|
this._setTheme(t) |
|
} |
|
}) |
|
} |
|
} |
|
|