mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-23 18:17:32 +00:00
235 lines
6.9 KiB
JavaScript
235 lines
6.9 KiB
JavaScript
/**
|
|
* Game list module.
|
|
* @version 1
|
|
*/
|
|
const gameList = (() => {
|
|
const TOP_POSITION = 102
|
|
const SELECT_THRESHOLD_MS = 160
|
|
|
|
const games = (() => {
|
|
let list = [], index = 0
|
|
return {
|
|
get index() {
|
|
return index
|
|
},
|
|
get list() {
|
|
return list
|
|
},
|
|
get selected() {
|
|
return list[index].title // selected by the game title, oof
|
|
},
|
|
set index(i) {
|
|
//-2 |
|
|
//-1 | |
|
|
// 0 < | <
|
|
// 1 | |
|
|
// 2 < < |
|
|
//+1 | |
|
|
//+2 |
|
|
index = i < -1 ? i = 0 :
|
|
i > list.length ? i = list.length - 1 :
|
|
(i % list.length + list.length) % list.length
|
|
},
|
|
set: (data = []) => list = data.sort((a, b) => a.title > b.title ? 1 : -1),
|
|
empty: () => list.length === 0
|
|
}
|
|
})()
|
|
|
|
const scroll = ((DEFAULT_INTERVAL) => {
|
|
const state = {
|
|
IDLE: 0, UP: -1, DOWN: 1, DRAG: 3
|
|
}
|
|
let last = state.IDLE
|
|
let _si
|
|
let onShift, onStop
|
|
|
|
const shift = (delta) => {
|
|
if (scroll.scrolling) return
|
|
onShift(delta)
|
|
// velocity?
|
|
// keep rolling the game list if the button is pressed
|
|
_si = setInterval(() => onShift(delta), DEFAULT_INTERVAL)
|
|
}
|
|
|
|
const stop = () => {
|
|
onStop()
|
|
_si && (clearInterval(_si) && (_si = null))
|
|
}
|
|
|
|
const handle = {[state.IDLE]: stop, [state.UP]: shift, [state.DOWN]: shift, [state.DRAG]: null}
|
|
|
|
return {
|
|
scroll: (move = state.IDLE) => {
|
|
handle[move] && handle[move](move)
|
|
last = move
|
|
},
|
|
get scrolling() {
|
|
return last !== state.IDLE
|
|
},
|
|
set onShift(fn) {
|
|
onShift = fn
|
|
},
|
|
set onStop(fn) {
|
|
onStop = fn
|
|
},
|
|
state,
|
|
last: () => last
|
|
}
|
|
})(SELECT_THRESHOLD_MS)
|
|
|
|
const ui = (() => {
|
|
const rootEl = document.getElementById('menu-container')
|
|
const choiceMarkerEl = document.getElementById('menu-item-choice')
|
|
|
|
const TRANSITION_DEFAULT = `top ${SELECT_THRESHOLD_MS}ms`
|
|
let listTopPos = TOP_POSITION
|
|
|
|
rootEl.style.transition = TRANSITION_DEFAULT
|
|
|
|
let onTransitionEnd = () => ({})
|
|
|
|
rootEl.addEventListener('transitionend', () => onTransitionEnd())
|
|
|
|
let items = []
|
|
|
|
const item = (parent) => {
|
|
const title = parent.firstChild.firstChild
|
|
const desc = parent.children[1]
|
|
|
|
const _desc = {
|
|
hide: () => gui.hide(desc),
|
|
show: async () => {
|
|
gui.show(desc)
|
|
await gui.anim.fadeIn(desc, .054321)
|
|
},
|
|
}
|
|
|
|
const _title = {
|
|
animate: () => title.classList.add('text-move'),
|
|
pick: () => title.classList.add('pick'),
|
|
reset: () => title.classList.remove('pick', 'text-move'),
|
|
}
|
|
|
|
const clear = () => {
|
|
_title.reset()
|
|
// _desc.hide()
|
|
}
|
|
|
|
return {
|
|
get description() {
|
|
return _desc
|
|
},
|
|
get title() {
|
|
return _title
|
|
},
|
|
clear,
|
|
}
|
|
}
|
|
|
|
const render = () => {
|
|
rootEl.innerHTML = games.list.map(game =>
|
|
`<div class="menu-item">` +
|
|
`<div><span>${game.title}</span></div>` +
|
|
//`<div class="menu-item__info">${game.system}</div>` +
|
|
`</div>`)
|
|
.join('')
|
|
items = [...rootEl.querySelectorAll('.menu-item')].map(x => item(x))
|
|
}
|
|
|
|
return {
|
|
get items() {
|
|
return items
|
|
},
|
|
get selected() {
|
|
return items[games.index]
|
|
},
|
|
get roundIndex() {
|
|
const closest = Math.round((listTopPos - TOP_POSITION) / -36)
|
|
return closest < 0 ? 0 :
|
|
closest > games.list.length - 1 ? games.list.length - 1 :
|
|
closest // don't wrap the list on drag
|
|
},
|
|
set onTransitionEnd(x) {
|
|
onTransitionEnd = x
|
|
},
|
|
set pos(idx) {
|
|
listTopPos = TOP_POSITION - idx * 36
|
|
rootEl.style.top = `${listTopPos}px`
|
|
},
|
|
drag: {
|
|
startPos: (pos) => {
|
|
rootEl.style.top = `${listTopPos - pos}px`
|
|
rootEl.style.transition = ''
|
|
},
|
|
stopPos: (pos) => {
|
|
listTopPos -= pos
|
|
rootEl.style.transition = TRANSITION_DEFAULT
|
|
},
|
|
},
|
|
render,
|
|
marker: {
|
|
show: () => gui.show(choiceMarkerEl)
|
|
},
|
|
NO_TRANSITION: onTransitionEnd(),
|
|
}
|
|
})(TOP_POSITION, SELECT_THRESHOLD_MS, games)
|
|
|
|
const show = () => {
|
|
ui.render()
|
|
ui.marker.show() // we show square pseudo-selection marker only after rendering
|
|
scroll.scroll(scroll.state.DOWN) // interactively moves games select down
|
|
scroll.scroll(scroll.state.IDLE)
|
|
}
|
|
|
|
const select = (index) => {
|
|
ui.items.forEach(i => i.clear()) // !to rewrite
|
|
games.index = index
|
|
ui.pos = games.index
|
|
}
|
|
|
|
scroll.onShift = (delta) => select(games.index + delta)
|
|
|
|
let hasTransition = true // needed for cases when MENU_RELEASE called instead MENU_PRESSED
|
|
|
|
scroll.onStop = () => {
|
|
const item = ui.selected
|
|
if (item) {
|
|
item.title.pick()
|
|
item.title.animate()
|
|
// hasTransition ? (ui.onTransitionEnd = item.description.show) : item.description.show()
|
|
}
|
|
}
|
|
|
|
event.sub(MENU_PRESSED, (position) => {
|
|
if (games.empty()) return
|
|
ui.onTransitionEnd = ui.NO_TRANSITION
|
|
hasTransition = false
|
|
scroll.scroll(scroll.state.DRAG)
|
|
ui.selected && ui.selected.clear()
|
|
ui.drag.startPos(position)
|
|
})
|
|
|
|
event.sub(MENU_RELEASED, (position) => {
|
|
if (games.empty()) return
|
|
ui.drag.stopPos(position)
|
|
select(ui.roundIndex)
|
|
hasTransition = !hasTransition
|
|
scroll.scroll(scroll.state.IDLE)
|
|
hasTransition = true
|
|
})
|
|
|
|
return {
|
|
scroll: (x) => {
|
|
if (games.empty()) return
|
|
scroll.scroll(x)
|
|
},
|
|
get selected() {
|
|
return games.selected
|
|
},
|
|
set: games.set,
|
|
show: () => {
|
|
if (games.empty()) return
|
|
show()
|
|
},
|
|
}
|
|
})(document, event, gui)
|