eruda3 / src /DevTools /DevTools.js
soiz1's picture
Update src/DevTools/DevTools.js
b8030e5 verified
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()
// Need a delay after show to enable transition effect.
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)
}
})
}
}