db-lgtv-focus-engine
Version:
the Best TV focus engine
249 lines (243 loc) • 7.4 kB
JavaScript
import Leaf from './Leaf'
import Trunk from './Trunk'
import LongScroll from './Trunk/LongScroll'
import Input from './Leaf/Input'
import Loading from "../tool/loading";
import Dialog from "../tool/dialog";
import {
bindResizeListener,
unbindResizeListener
} from '../event/resize'
import {
bindKeydownListener,
unbindKeydownListener
} from '../event/keydown'
import {
bindKeyupListener,
unbindKeyupListener
} from '../event/keyup'
import {
bindWheelListener,
unbindWheelListener
} from '../event/wheel'
import {
bindHashListener,
unbindHashListener
} from '../event/hashchange'
import {
bindMutationObserver,
unbindMutationObserver
} from '../event/mutation'
import config from '../config.json'
class FEngine {
constructor(opt = {}) {
let option = {
...config,
...opt
}
this.initData(option)
this.use(Loading)
this.use(Dialog)
this.activated()
window.$engine = this
}
use(plugin) {
plugin.install(this)
}
initData(opt) {
this.options = opt
this.history = []
this.leaf_pool = []
this.trunk_pool = []
this.cached_map = {}
this.focused_map = {}
this.freezed_map = {}
this.stash_map = {}
this.last_key_down = null
this.last_key_up = null
this.history_focused_id = null
this.$tick_callback_stack = []
this.$scroll_callback_stack = []
}
findLeafById(id) {
let leaf = this.leaf_pool.find(_i => _i.id === id)
return leaf
}
findLeafByDom(el) {
let id = el.getAttribute('db-id') || window.getIdByDom(el)
return this.findLeafById(id)
}
// 根据id获取trunk
findTrunkById(id) {
let trunk = this.trunk_pool.find(_i => _i.id === id)
return trunk
}
findTrunkByDom(el) {
let id = window.getIdByDom(el)
return this.findTrunkById(id)
}
findFocusedLeaf() {
return this.focused_map[window.location.href] ? this.findLeafById(this.focused_map[window.location.href]) : null
}
activated() {
bindResizeListener(this)
bindKeydownListener(this)
bindKeyupListener(this)
bindWheelListener(this)
bindMutationObserver(this)
bindHashListener(this)
this.render()
}
deactivated() {
unbindResizeListener(this)
unbindKeydownListener(this)
unbindKeyupListener(this)
unbindWheelListener(this)
unbindMutationObserver(this)
unbindHashListener(this)
}
destroyed() {
this.deactivated()
this.forget()
}
forget({
cache,
focus,
freeze
} = {
cache: true,
focus: true,
freeze: true
}) {
cache && delete(this.cached_map[window.location.href])
focus && delete(this.focused_map[window.location.href])
freeze && delete(this.freezed_map[window.location.href])
this.render()
}
clear() {
let focus_pool = document.querySelectorAll('[db-focus]')
Array.from(focus_pool).forEach(_i => {
_i.removeAttribute('db-focus')
})
let child_focus_pool = document.querySelectorAll('[db-child-focus]')
Array.from(child_focus_pool).forEach(_i => {
_i.removeAttribute('db-child-focus')
})
let freeze_pool = document.querySelectorAll('[db-freeze]')
Array.from(freeze_pool).forEach(_i => {
_i.removeAttribute('db-freeze')
})
let cache_pool = document.querySelectorAll('[db-cache]')
Array.from(cache_pool).forEach(_i => {
_i.removeAttribute('db-cache')
})
}
render() {
this.clear()
// 初始化叶子元素池
let leaf_pool = document.querySelectorAll('[db-leaf]')
this.leaf_pool = Array.from(leaf_pool).map(_i => {
if (_i.tagName === 'INPUT') return new Input(this, _i)
return new Leaf(this, _i)
}).filter(_i => _i);
// 初始化枝干元素池
let trunk_pool = [
...Array.from(document.querySelectorAll('[db-trunk]')),
...Array.from(document.querySelectorAll('[db-scroll]')),
...Array.from(document.querySelectorAll('[db-long-scroll]'))
]
this.trunk_pool = trunk_pool.map(_i => {
if (_i.hasAttribute('db-scroll')) return new Trunk(this, _i)
if (_i.hasAttribute('db-long-scroll')) return new LongScroll(this, _i)
return new Trunk(this, _i)
});
// 恢复页面状态
this.remember()
this.$tickCallback()
}
remember() {
// 恢复缓存状态
let cached_map = this.cached_map[window.location.href]
for (let id in cached_map) {
let leaf = this.findLeafById(id)
leaf && leaf.cache()
}
// 恢复冻结状态
let freezed_map = this.freezed_map[window.location.href]
for (let id in freezed_map) {
let leaf = this.findLeafById(id)
leaf && leaf.freeze()
}
// 恢复焦点状态
let focused_id = this.focused_map[window.location.href]
let leaf = this.findLeafById(focused_id)
if (!leaf || leaf.freezed) leaf = this.leaf_pool.filter(_i => !_i.freezed && _i.isLeafVisibleFor() && _i.el.hasAttribute('db-default'))[0] || this.leaf_pool.filter(_i => !_i.freezed && _i.isLeafVisibleFor())[0]
leaf && leaf.focus()
}
stash(types, key) {
let stash = {}
types.forEach((_i) => {
stash[_i] = JSON.parse(JSON.stringify(this[`${_i}_map`][window.location.href] || {}))
})
this.stash_map[key] = stash
}
pop(key) {
if (!this.stash_map[key]) return
for (let _i in this.stash_map[key]) {
this[`${_i}_map`][window.location.href] = this.stash_map[key][_i]
}
this.render()
this.stash_map[key] = null
}
freeze() {
this.leaf_pool.forEach(leaf => leaf.freeze({
render: false
}))
this.render()
}
unfreeze() {
this.leaf_pool.forEach(leaf => leaf.unfreeze({
render: false
}))
this.render()
}
$nextTick(callback) {
this.$tick_callback_stack.push(callback)
}
$tickCallback() {
let stack = this.$tick_callback_stack
this.$tick_callback_stack = []
stack.forEach(_i => {
_i()
})
}
$nextScroll(callback) {
this.$scroll_callback_stack.push(callback)
}
$scrollCallback() {
let stack = this.$scroll_callback_stack
this.$scroll_callback_stack = []
stack.forEach(_i => {
_i()
})
}
reverse() {
let target_step = null
let _distance = 0
while(_distance < 2) {
let step = this.history.pop()
if (step.cmd === 'focus') {
target_step = step
_distance ++
}
}
if (target_step.leaf_id) {
let leaf = this.findLeafById(target_step.leaf_id)
leaf && leaf.focus()
}
}
}
FEngine.prototype.Leaf = Leaf
FEngine.prototype.Trunk = Trunk
FEngine.prototype.LongScroll = LongScroll
export default FEngine