taiko-web/public/src/js/settings.js
KatieFrogs e43c4afceb Lyrics, search, and other fixes
- #LYRIC
  - Parse #LYRIC commands and apply them to all difficulties that do not have them
  - #LYRIC command now supports branches
  - Fix last #LYRIC at the end of the chart getting ignored
- Fix the glitchy dragging and dropping of files on the custom song importing page
- Fix Ctrl and Shift keys getting stuck on song select when switching tabs with Ctrl(+Shift)+Tab
- Search
  - Fix the search box "random:yes" query to randomize the entire results and not just the first 50
  - Add "all:yes" query to the search box to remove the result limit and display all of the results
  - Fix searching for an invalid query (like "cleared:yes" or ":") unexpectedly returning all the songs
  - Fix pressing Q then jumping to a song through search not unmuting the sound
  - Pressing the search key on mobile will hide the keyboard
  - Fix search tips changing rapidly when the window is resized
- Use comments instead of `######` in the issue template so that the warning does not appear in the issue
- Fix TJA MAKER: url between angle brackets not working
- Add a check for Class field declarations in the browser support warning
- Fix gpicker getting stuck if a network error occurs
- Fix not being able to replace some assets using a "taiko-web assets" folder
- Fix selectable song title not being aligned with the game if the game window is too wide
- Allow plugin developers to use the "select" type for the settings options
  - It uses "options" array and "options_lang" object
- Fix plugins not getting removed from the plugin list on syntax error
- Fix error messages not working if a default plugin is broken
- Fix the start of default plugins not stopping the page from loading on error
- Fix not being able to scroll the plugins screen on mobile
2022-07-15 16:00:43 +02:00

1314 lines
39 KiB
JavaScript

class Settings{
constructor(...args){
this.init(...args)
}
init(){
var ios = /iPhone|iPad/.test(navigator.userAgent)
var phone = /Android|iPhone|iPad/.test(navigator.userAgent)
this.allLanguages = []
for(var i in allStrings){
this.allLanguages.push(i)
}
this.items = {
language: {
type: "language",
options: this.allLanguages,
default: this.getLang()
},
resolution: {
type: "select",
options: ["high", "medium", "low", "lowest"],
default: phone ? "medium" : "high"
},
touchAnimation: {
type: "toggle",
default: !ios,
touch: true
},
keyboardSettings: {
type: "keyboard",
default: {
ka_l: ["d"],
don_l: ["f"],
don_r: ["j"],
ka_r: ["k"]
},
touch: false
},
gamepadLayout: {
type: "gamepad",
options: ["a", "b", "c"],
default: "a",
gamepad: true
},
latency: {
type: "latency",
default: {
"audio": 0,
"video": 0,
"drumSounds": true
}
},
easierBigNotes: {
type: "toggle",
default: false
},
showLyrics: {
type: "toggle",
default: true
}
}
this.storage = {}
try{
var storage = JSON.parse(localStorage.getItem("settings") || "{}")
for(var i in this.items){
var current = this.items[i]
if(current.type === "language"){
this.storage[i] = localStorage.getItem("lang")
if(current.options.indexOf(this.storage[i]) === -1){
this.storage[i] = null
}
}else if(i in storage){
if((current.type === "select" || current.type === "gamepad") && current.options.indexOf(storage[i]) === -1){
this.storage[i] = null
}else if(current.type === "keyboard"){
var obj = {}
for(var j in current.default){
if(storage[i] && storage[i][j] && storage[i][j][0]){
obj[j] = storage[i][j]
}else{
obj = null
break
}
}
this.storage[i] = obj
}else if(current.type === "latency"){
var obj = {}
for(var j in current.default){
if(storage[i] && j in storage[i]){
if(j === "drumSounds"){
obj[j] = !!storage[i][j]
continue
}else if(!isNaN(storage[i][j])){
obj[j] = Math.round(parseFloat(storage[i][j]) || 0)
continue
}
}
obj = null
break
}
this.storage[i] = obj
}else{
this.storage[i] = storage[i]
}
}else{
this.storage[i] = null
}
}
}catch(e){
for(var i in this.items){
this.storage[i] = null
}
}
}
getItem(name){
var value = this.storage[name]
return value === null ? this.items[name].default : value
}
setItem(name, value){
this.storage[name] = value
try{
if(name === "language"){
if(value){
localStorage.setItem("lang", value)
}else{
localStorage.removeItem("lang")
}
}else{
var language = this.storage.language
delete this.storage.language
localStorage.setItem("settings", JSON.stringify(this.storage))
this.storage.language = language
}
}catch(e){}
}
getLang(){
if("languages" in navigator){
var userLang = navigator.languages.slice()
userLang.unshift(navigator.language)
for(var i in userLang){
for(var j in allStrings){
if(allStrings[j].regex.test(userLang[i])){
return j
}
}
}
}
return this.allLanguages[0]
}
setLang(lang, noEvent){
strings = lang
var boldFonts = strings.font === "Microsoft YaHei, sans-serif"
loader.screen.style.fontFamily = strings.font
loader.screen.style.fontWeight = boldFonts ? "bold" : ""
loader.screen.classList[boldFonts ? "add" : "remove"]("bold-fonts")
strings.plural = new Intl.PluralRules(lang.intl)
if(!noEvent){
pageEvents.send("language-change", lang.id)
}
}
addLang(lang, forceSet){
allStrings[lang.id] = lang
if(lang.categories){
assets.categories.forEach(category => {
if("title_lang" in category && lang.categories[category.title_lang.en]){
category.title_lang[lang.id] = lang.categories[category.title_lang.en]
}
})
}
languageList.push(lang.id)
this.allLanguages.push(lang.id)
this.items.language.default = this.getLang()
if(forceSet){
this.storage.language = lang.id
}else{
try{
this.storage.language = localStorage.getItem("lang")
}catch(e){}
if(this.items.language.options.indexOf(this.storage.language) === -1){
this.storage.language = null
}
}
if(settings.getItem("language") === lang.id){
settings.setLang(lang)
}
}
removeLang(lang){
delete allStrings[lang.id]
assets.categories.forEach(category => {
if("title_lang" in category){
delete category.title_lang[lang.id]
}
})
var index = languageList.indexOf(lang.id)
if(index !== -1){
languageList.splice(index, 1)
}
var index = this.allLanguages.indexOf(lang.id)
if(index !== -1){
this.allLanguages.splice(index, 1)
}
this.items.language.default = this.getLang()
try{
this.storage.language = localStorage.getItem("lang")
}catch(e){}
if(this.items.language.options.indexOf(this.storage.language) === -1){
this.storage.language = null
}
if(lang.id === strings.id){
settings.setLang(allStrings[this.getItem("language")])
}
}
}
class SettingsView{
constructor(...args){
this.init(...args)
}
init(touchEnabled, tutorial, songId, toSetting, settingsItems, noSoundStart){
this.touchEnabled = touchEnabled
this.tutorial = tutorial
this.songId = songId
this.customSettings = !!settingsItems
this.settingsItems = settingsItems || settings.items
this.locked = false
loader.changePage("settings", tutorial)
if(!noSoundStart){
assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992)
}
this.defaultButton = document.getElementById("settings-default")
this.viewOuter = this.getElement("view-outer")
if(touchEnabled){
this.viewOuter.classList.add("touch-enabled")
}
this.touchEnd = []
this.windowSymbol = Symbol()
this.touchMove = {
active: false,
x: 0,
y: 0
}
pageEvents.add(window, ["mouseup", "touchstart", "touchmove", "touchend", "blur"], event => {
var move = this.touchMove
if(event.type === "touchstart"){
var cursor = event.changedTouches[0]
move.active = false
move.x = cursor.pageX
move.y = cursor.pageY
}else if(event.type === "touchmove"){
var cursor = event.changedTouches[0]
if (Math.abs(move.x - cursor.pageX) > 10 || Math.abs(move.y - cursor.pageY) > 10){
move.active = true
}
}else{
this.touchEnd.forEach(func => func(event))
move.active = false
}
}, this.windowSymbol)
if(this.customSettings){
pageEvents.add(window, "language-change", event => this.setLang(), this.windowSymbol)
}
var gamepadEnabled = false
if("getGamepads" in navigator){
var gamepads = navigator.getGamepads()
for(var i = 0; i < gamepads.length; i++){
if(gamepads[i]){
gamepadEnabled = true
break
}
}
}
this.mode = "settings"
this.pressedKeys = {}
this.keyboard = new Keyboard({
"confirm": ["enter", "space", "don_l", "don_r"],
"up": ["up"],
"right": ["right", "ka_r"],
"down": ["down"],
"left": ["left", "ka_l"],
"back": ["esc"],
"other": ["wildcard"]
}, this.keyPressed.bind(this))
this.gamepad = new Gamepad({
"confirm": ["b", "ls", "rs"],
"up": ["u", "lsu"],
"right": ["r", "rb", "rt", "lsr"],
"down": ["d", "lsd"],
"left": ["l", "lb", "lt", "lsl"],
"back": ["start", "a"]
}, this.keyPressed.bind(this))
this.viewTitle = this.getElement("view-title")
this.endButton = this.getElement("view-end-button")
this.resolution = settings.getItem("resolution")
var content = this.getElement("view-content")
this.items = []
this.selected = 0
for(let i in this.settingsItems){
var current = this.settingsItems[i]
if(
!touchEnabled && current.touch === true ||
touchEnabled && current.touch === false ||
!gamepadEnabled && current.gamepad === true
){
continue
}
var settingBox = document.createElement("div")
settingBox.classList.add("setting-box")
if(current.indent){
settingBox.style.marginLeft = (2 * current.indent || 0).toString() + "em"
}
var nameDiv = document.createElement("div")
nameDiv.classList.add("setting-name", "stroke-sub")
if(current.name || current.name_lang){
var name = this.getLocalTitle(current.name, current.name_lang)
}else{
var name = strings.settings[i].name
}
this.setAltText(nameDiv, name)
if(current.description || current.description_lang){
settingBox.title = this.getLocalTitle(current.description, current.description_lang) || ""
}
settingBox.appendChild(nameDiv)
var valueDiv = document.createElement("div")
valueDiv.classList.add("setting-value")
let outputObject = {
id: i,
settingBox: settingBox,
nameDiv: nameDiv,
valueDiv: valueDiv,
name: current.name,
name_lang: current.name_lang,
description: current.description,
description_lang: current.description_lang
}
if(current.type === "number"){
["min", "max", "fixedPoint", "step", "sign", "format", "format_lang"].forEach(opt => {
if(opt in current){
outputObject[opt] = current[opt]
}
})
outputObject.valueText = document.createTextNode("")
valueDiv.appendChild(outputObject.valueText)
var buttons = document.createElement("div")
buttons.classList.add("latency-buttons")
buttons.title = ""
var buttonMinus = document.createElement("span")
buttonMinus.innerText = "-"
buttons.appendChild(buttonMinus)
this.addTouchRepeat(buttonMinus, event => {
this.numberAdjust(outputObject, -1)
})
var buttonPlus = document.createElement("span")
buttonPlus.innerText = "+"
buttons.appendChild(buttonPlus)
this.addTouchRepeat(buttonPlus, event => {
this.numberAdjust(outputObject, 1)
})
valueDiv.appendChild(buttons)
this.addTouch(settingBox, event => {
if(event.target.tagName !== "SPAN"){
this.setValue(i)
}
}, true)
}else{
this.addTouchEnd(settingBox, event => this.setValue(i))
}
settingBox.appendChild(valueDiv)
content.appendChild(settingBox)
if(!toSetting && this.items.length === this.selected || toSetting === i){
this.selected = this.items.length
settingBox.classList.add("selected")
}
this.items.push(outputObject)
this.getValue(i, valueDiv)
}
var selectBack = this.items.length === 0
if(this.customSettings){
var form = document.createElement("form")
this.browse = document.createElement("input")
this.browse.id = "plugin-browse"
this.browse.type = "file"
this.browse.multiple = true
this.browse.accept = ".taikoweb.js"
pageEvents.add(this.browse, "change", this.browseChange.bind(this))
form.appendChild(this.browse)
this.browseButton = document.createElement("div")
this.browseButton.classList.add("taibtn", "stroke-sub", "plugin-browse-button")
this.browseText = document.createTextNode("")
this.browseButton.appendChild(this.browseText)
this.browseButton.appendChild(form)
this.defaultButton.parentNode.insertBefore(this.browseButton, this.defaultButton)
this.items.push({
id: "browse",
settingBox: this.browseButton
})
}
this.showDefault = !this.customSettings || plugins.allPlugins.filter(obj => obj.plugin.imported).length
if(this.showDefault){
this.items.push({
id: "default",
settingBox: this.defaultButton
})
this.addTouch(this.defaultButton, this.defaultSettings.bind(this))
}else{
this.defaultButton.parentNode.removeChild(this.defaultButton)
}
this.items.push({
id: "back",
settingBox: this.endButton
})
this.addTouch(this.endButton, this.onEnd.bind(this))
if(selectBack){
this.selected = this.items.length - 1
this.endButton.classList.add("selected")
}
if(!this.customSettings){
this.gamepadSettings = document.getElementById("settings-gamepad")
this.addTouch(this.gamepadSettings, event => {
if(event.target === event.currentTarget){
this.gamepadBack()
}
})
this.gamepadTitle = this.gamepadSettings.getElementsByClassName("view-title")[0]
this.gamepadEndButton = this.gamepadSettings.getElementsByClassName("view-end-button")[0]
this.addTouch(this.gamepadEndButton, event => this.gamepadBack(true))
this.gamepadBox = this.gamepadSettings.getElementsByClassName("setting-box")[0]
this.addTouch(this.gamepadBox, event => this.gamepadSet(1))
this.gamepadButtons = document.getElementById("gamepad-buttons")
this.gamepadValue = document.getElementById("gamepad-value")
this.latencySettings = document.getElementById("settings-latency")
this.addTouch(this.latencySettings, event => {
if(event.target === event.currentTarget){
this.latencyBack()
}
})
this.latencyTitle = this.latencySettings.getElementsByClassName("view-title")[0]
this.latencyItems = []
this.latencySelected = 0
var latencyContent = this.latencySettings.getElementsByClassName("view-content")[0]
var latencyWindow = ["calibration", "audio", "video", "drumSounds"]
for(let i in latencyWindow){
let current = latencyWindow[i]
var settingBox = document.createElement("div")
settingBox.classList.add("setting-box")
var nameDiv = document.createElement("div")
nameDiv.classList.add("setting-name", "stroke-sub")
var name = strings.settings.latency[current]
this.setAltText(nameDiv, name)
settingBox.appendChild(nameDiv)
let outputObject = {
id: current,
settingBox: settingBox,
nameDiv: nameDiv
}
if(current === "calibration"){
nameDiv.style.width = "100%"
}else{
var valueDiv = document.createElement("div")
valueDiv.classList.add("setting-value")
settingBox.appendChild(valueDiv)
var valueText = document.createTextNode("")
valueDiv.appendChild(valueText)
this.latencyGetValue(current, valueText)
if(current !== "drumSounds"){
var buttons = document.createElement("div")
buttons.classList.add("latency-buttons")
var buttonMinus = document.createElement("span")
buttonMinus.innerText = "-"
buttons.appendChild(buttonMinus)
this.addTouchRepeat(buttonMinus, event => {
this.latencySetAdjust(outputObject, -1)
})
var buttonPlus = document.createElement("span")
buttonPlus.innerText = "+"
buttons.appendChild(buttonPlus)
this.addTouchRepeat(buttonPlus, event => {
this.latencySetAdjust(outputObject, 1)
})
valueDiv.appendChild(buttons)
}
}
latencyContent.appendChild(settingBox)
if(this.latencyItems.length === this.latencySelected){
settingBox.classList.add("selected")
}
this.addTouch(settingBox, event => {
if(event.target.tagName !== "SPAN"){
this.latencySetValue(current, event.type === "touchend")
}
}, true)
if(current !== "calibration"){
outputObject.valueDiv = valueDiv
outputObject.valueText = valueText
outputObject.buttonMinus = buttonMinus
outputObject.buttonPlus = buttonPlus
}
this.latencyItems.push(outputObject)
}
this.latencyDefaultButton = document.getElementById("latency-default")
this.latencyItems.push({
id: "default",
settingBox: this.latencyDefaultButton
})
this.addTouch(this.latencyDefaultButton, event => this.latencyDefault())
this.latencyEndButton = this.latencySettings.getElementsByClassName("view-end-button")[0]
this.latencyItems.push({
id: "back",
settingBox: this.latencyEndButton
})
this.addTouch(this.latencyEndButton, event => this.latencyBack(true))
}
this.setStrings()
this.drumSounds = settings.getItem("latency").drumSounds
this.playedSounds = {}
this.redrawRunning = true
this.redrawBind = this.redraw.bind(this)
this.redraw()
if(toSetting === "latency"){
this.mode = "latency"
this.latencySet()
}
if(this.customSettings){
pageEvents.send("plugins")
}else{
pageEvents.send("settings")
}
}
getElement(name){
return loader.screen.getElementsByClassName(name)[0]
}
addTouch(element, callback, end){
var touchEvent = end ? "touchend" : "touchstart"
pageEvents.add(element, ["mousedown", touchEvent], event => {
if(event.type === touchEvent){
if(event.cancelable){
event.preventDefault()
}
this.touched = true
}else if(event.which !== 1){
return
}else{
this.touched = false
}
if(event.type !== "touchend" || !this.touchMove.active){
callback(event)
}
})
}
addTouchEnd(element, callback){
this.addTouch(element, callback, true)
}
addTouchRepeat(element, callback){
this.addTouch(element, event => {
var active = true
var func = () => {
active = false
this.touchEnd.splice(this.touchEnd.indexOf(func), 1)
}
this.touchEnd.push(func)
var repeat = delay => {
if(active){
callback()
setTimeout(() => repeat(50), delay)
}
}
repeat(400)
})
}
removeTouch(element){
pageEvents.remove(element, ["mousedown", "touchstart"])
}
removeTouchEnd(element){
pageEvents.remove(element, ["mousedown", "touchend"])
}
getValue(name, valueDiv){
if(!this.items){
return
}
var current = this.settingsItems[name]
if(current.getItem){
var value = current.getItem()
}else{
var value = settings.getItem(name)
}
if(current.type === "language"){
value = allStrings[value].name + " (" + value + ")"
}else if(current.type === "select" || current.type === "gamepad"){
if(current.options_lang && current.options_lang[value]){
value = this.getLocalTitle(value, current.options_lang[value])
}else if(!current.getItem){
value = strings.settings[name][value]
}
}else if(current.type === "toggle"){
value = value ? strings.settings.on : strings.settings.off
}else if(current.type === "keyboard"){
valueDiv.innerHTML = ""
for(var i in value){
var keyDiv = document.createElement("div")
keyDiv.style.color = i === "ka_l" || i === "ka_r" ? "#009aa5" : "#ef2c10"
var key = value[i][0]
for(var j in this.keyboard.substitute){
if(this.keyboard.substitute[j] === key){
key = j
break
}
}
keyDiv.innerText = key.toUpperCase()
valueDiv.appendChild(keyDiv)
}
return
}else if(current.type === "latency"){
var audioVideo = [Math.round(value.audio), Math.round(value.video)]
var latencyValue = strings.settings[name].value.split("%s")
var latencyIndex = 0
value = ""
latencyValue.forEach((string, i) => {
if(i !== 0){
value += this.addMs(audioVideo[latencyIndex++])
}
value += string
})
}else if(current.type === "number"){
var mul = Math.pow(10, current.fixedPoint || 0)
this.items[name].value = value * mul
value = Intl.NumberFormat(strings.intl, current.sign ? {
signDisplay: "always"
} : undefined).format(value)
if(current.format || current.format_lang){
value = this.getLocalTitle(current.format, current.format_lang).replace("%s", value)
}
this.items[name].valueText.data = value
return
}
valueDiv.innerText = value
}
setValue(name){
if(this.locked){
return
}
var promise
var current = this.settingsItems[name]
if(current.getItem){
var value = current.getItem()
}else{
var value = settings.getItem(name)
}
var selectedIndex = this.items.findIndex(item => item.id === name)
var selected = this.items[selectedIndex]
if(this.mode !== "settings"){
if(this.mode === "number"){
return this.numberBack(this.items[this.selected])
}
if(this.selected === selectedIndex){
this.keyboardBack(selected)
this.playSound("se_don")
}
return
}
if(this.selected !== selectedIndex){
this.items[this.selected].settingBox.classList.remove("selected")
this.selected = selectedIndex
selected.settingBox.classList.add("selected")
}
if(current.type === "language" || current.type === "select"){
value = current.options[this.mod(current.options.length, current.options.indexOf(value) + 1)]
}else if(current.type === "toggle"){
value = !value
}else if(current.type === "keyboard"){
this.mode = "keyboard"
selected.settingBox.style.animation = "none"
selected.valueDiv.classList.add("selected")
this.keyboardKeys = {}
this.keyboardSet()
this.playSound("se_don")
return
}else if(current.type === "gamepad"){
this.mode = "gamepad"
this.gamepadSelected = current.options.indexOf(value)
this.gamepadSet()
this.playSound("se_don")
return
}else if(current.type === "latency"){
this.mode = "latency"
this.latencySet()
this.playSound("se_don")
return
}else if(current.type === "number"){
this.mode = "number"
selected.settingBox.style.animation = "none"
selected.valueDiv.classList.add("selected")
this.playSound("se_don")
return
}
if(current.setItem){
promise = current.setItem(value)
}else{
settings.setItem(name, value)
}
(promise || Promise.resolve()).then(() => {
this.getValue(name, this.items[this.selected].valueDiv)
this.playSound("se_ka")
if(current.type === "language"){
this.setLang(allStrings[value])
}
})
}
keyPressed(pressed, name, event, repeat){
if(this.locked){
return
}
if(pressed){
if(!this.pressedKeys[name]){
this.pressedKeys[name] = this.getMS() + 300
}
}else{
this.pressedKeys[name] = 0
return
}
if(repeat && name !== "up" && name !== "right" && name !== "down" && name !== "left"){
return
}
this.touched = false
var selected = this.items[this.selected]
if(this.mode === "settings"){
if(name === "confirm"){
if(selected.id === "back"){
this.onEnd()
}else if(selected.id === "default"){
this.defaultSettings()
}else if(selected.id === "browse"){
if(event){
this.playSound("se_don")
this.browse.click()
}
}else{
this.setValue(selected.id)
}
}else if(name === "up" || name === "right" || name === "down" || name === "left"){
selected.settingBox.classList.remove("selected")
do{
this.selected = this.mod(this.items.length, this.selected + ((name === "right" || name === "down") ? 1 : -1))
}while((this.items[this.selected].id === "default" || this.items[this.selected].id === "browse") && name !== "left")
selected = this.items[this.selected]
selected.settingBox.classList.add("selected")
this.scrollTo(selected.settingBox)
this.playSound("se_ka")
}else if(name === "back"){
this.onEnd()
}
}else if(this.mode === "gamepad"){
if(name === "confirm"){
this.gamepadBack(true)
}else if(name === "up" || name === "right" || name === "down" || name === "left"){
this.gamepadSet((name === "right" || name === "down") ? 1 : -1)
}else if(name === "back"){
this.gamepadBack()
}
}else if(this.mode === "keyboard"){
if(name === "back"){
this.keyboardBack(selected)
this.playSound("se_cancel")
}else if(event){
event.preventDefault()
var currentKey = event.key.toLowerCase()
for(var i in this.keyboardKeys){
if(this.keyboardKeys[i][0] === currentKey || !currentKey){
return
}
}
var current = this.keyboardCurrent
this.playSound(current === "ka_l" || current === "ka_r" ? "se_ka" : "se_don")
this.keyboardKeys[current] = [currentKey]
this.keyboardSet()
}
}else if(this.mode === "latency"){
var latencySelected = this.latencyItems[this.latencySelected]
if(name === "confirm"){
if(latencySelected.id === "back"){
this.latencyBack(true)
}else if(latencySelected.id === "default"){
this.latencyDefault()
}else{
this.latencySetValue(latencySelected.id)
}
}else if(name === "up" || name === "right" || name === "down" || name === "left"){
latencySelected.settingBox.classList.remove("selected")
do{
this.latencySelected = this.mod(this.latencyItems.length, this.latencySelected + ((name === "right" || name === "down") ? 1 : -1))
}while(this.latencyItems[this.latencySelected].id === "default" && name !== "left")
latencySelected = this.latencyItems[this.latencySelected]
latencySelected.settingBox.classList.add("selected")
latencySelected.settingBox.scrollIntoView()
this.playSound("se_ka")
}else if(name === "back"){
this.latencyBack()
}
}else if(this.mode === "latencySet"){
var latencySelected = this.latencyItems[this.latencySelected]
if(name === "confirm" || name === "back"){
this.latencySetBack(latencySelected)
this.playSound(name === "confirm" ? "se_don" : "se_cancel")
}else if(name === "up" || name === "right" || name === "down" || name === "left"){
this.latencySetAdjust(latencySelected, (name === "up" || name === "right") ? 1 : -1)
if(event){
event.preventDefault()
}
}
}else if(this.mode === "number"){
if(name === "confirm" || name === "back"){
this.numberBack(selected)
this.playSound(name === "confirm" ? "se_don" : "se_cancel")
}else if(name === "up" || name === "right" || name === "down" || name === "left"){
this.numberAdjust(selected, (name === "up" || name === "right") ? 1 : -1)
if(event){
event.preventDefault()
}
}
}
}
scrollTo(element){
var parentNode = element.parentNode
var selected = element.getBoundingClientRect()
var parent = parentNode.getBoundingClientRect()
var scrollY = parentNode.scrollTop
var selectedPosTop = selected.top - selected.height / 2
if(Math.floor(selectedPosTop) < Math.floor(parent.top)){
parentNode.scrollTop += selectedPosTop - parent.top
}else{
var selectedPosBottom = selected.top + selected.height * 1.5 - parent.top
if(Math.floor(selectedPosBottom) > Math.floor(parent.height)){
parentNode.scrollTop += selectedPosBottom - parent.height
}
}
}
keyboardSet(){
var selected = this.items[this.selected]
var current = this.settingsItems[selected.id]
selected.valueDiv.innerHTML = ""
for(var i in current.default){
var keyDiv = document.createElement("div")
keyDiv.style.color = i === "ka_l" || i === "ka_r" ? "#009aa5" : "#ef2c10"
if(this.keyboardKeys[i]){
var key = this.keyboardKeys[i][0]
for(var j in this.keyboard.substitute){
if(this.keyboard.substitute[j] === key){
key = j
break
}
}
keyDiv.innerText = key.toUpperCase()
selected.valueDiv.appendChild(keyDiv)
}else{
keyDiv.innerText = "[" + strings.settings[selected.id][i] + "]"
selected.valueDiv.appendChild(keyDiv)
this.keyboardCurrent = i
return
}
}
settings.setItem(selected.id, this.keyboardKeys)
this.keyboardBack(selected)
this.keyboard.update()
pageEvents.setKbd()
}
keyboardBack(selected){
this.mode = "settings"
selected.settingBox.style.animation = ""
selected.valueDiv.classList.remove("selected")
this.getValue(selected.id, selected.valueDiv)
}
gamepadSet(diff){
if(this.mode !== "gamepad"){
return
}
var selected = this.items[this.selected]
var current = this.settingsItems[selected.id]
if(diff){
this.gamepadSelected = this.mod(current.options.length, this.gamepadSelected + diff)
this.playSound("se_ka")
}
var opt = current.options[this.gamepadSelected]
var value = strings.settings[selected.id][opt]
this.setAltText(this.gamepadValue, value)
this.gamepadButtons.style.backgroundPosition = "0 " + (-11.87 - 4.93 * this.gamepadSelected) + "em"
this.gamepadSettings.style.display = "flex"
}
gamepadBack(confirm){
if(this.mode !== "gamepad"){
return
}
var selected = this.items[this.selected]
var current = this.settingsItems[selected.id]
settings.setItem(selected.id, current.options[this.gamepadSelected])
this.getValue(selected.id, selected.valueDiv)
this.playSound(confirm ? "se_don" : "se_cancel")
this.gamepadSettings.style.display = ""
this.mode = "settings"
}
latencySet(){
if(this.mode !== "latency"){
return
}
var selected = this.items[this.selected]
var current = this.settingsItems[selected.id]
this.latencySettings.style.display = "flex"
}
latencyGetValue(name, valueText){
var currentLatency = settings.getItem("latency")
if(name === "drumSounds"){
valueText.data = currentLatency[name] ? strings.settings.on : strings.settings.off
}else{
valueText.data = this.addMs(currentLatency[name] || 0)
}
}
latencySetValue(name, touched){
var selectedIndex = this.latencyItems.findIndex(item => item.id === name)
var selected = this.latencyItems[selectedIndex]
if(this.mode === "latencySet"){
this.latencySetBack(this.latencyItems[this.latencySelected])
if(this.latencySelected === selectedIndex){
this.playSound("se_don")
return
}
}else if(this.mode !== "latency"){
return
}
if(name === "calibration"){
this.playSound("se_don")
this.clean()
new LoadSong({
"title": strings.calibration.title,
"folder": "calibration",
"type": "tja",
"songSkin": {}
}, false, false, touched)
}else if(name === "drumSounds"){
this.drumSounds = !settings.getItem("latency")[name]
this.latencySave(name, this.drumSounds)
this.latencyGetValue(name, selected.valueText)
this.playSound("se_don")
}else{
var value = Math.round(settings.getItem("latency")[name] || 0)
if(this.latencySelected !== selectedIndex){
this.latencyItems[this.latencySelected].settingBox.classList.remove("selected")
this.latencySelected = selectedIndex
selected.settingBox.classList.add("selected")
}
this.mode = "latencySet"
selected.settingBox.style.animation = "none"
selected.valueDiv.classList.add("selected")
selected.value = value
this.playSound("se_don")
}
}
latencySetAdjust(selected, add){
selected.value += add
if(selected.value > 500){
selected.value = 500
}else if(selected.value < -200){
selected.value = -200
}else{
this.playSound("se_ka")
}
selected.valueText.data = this.addMs(selected.value)
}
latencySetBack(selected){
this.mode = "latency"
selected.settingBox.style.animation = ""
selected.valueDiv.classList.remove("selected")
this.latencySave(selected.id, selected.value)
this.latencyGetValue(selected.id, selected.valueText)
}
latencySave(id, value){
var input = settings.getItem("latency")
var output = {}
for(var i in input){
if(i === id){
output[i] = value
}else{
output[i] = input[i]
}
}
settings.setItem("latency", output)
}
latencyDefault(){
if(this.mode === "latencySet"){
this.latencySetBack(this.latencyItems[this.latencySelected])
}else if(this.mode !== "latency"){
return
}
settings.setItem("latency", null)
this.latencyItems.forEach(item => {
if(item.id === "audio" || item.id === "video" || item.id === "drumSounds"){
this.latencyGetValue(item.id, item.valueText)
}
})
this.drumSounds = settings.getItem("latency").drumSounds
this.playSound("se_don")
}
latencyBack(confirm){
if(this.mode === "latencySet"){
this.latencySetBack(this.latencyItems[this.latencySelected])
if(!confirm){
this.playSound("se_don")
return
}
}
if(this.mode !== "latency"){
return
}
var selected = this.items[this.selected]
var current = this.settingsItems[selected.id]
this.getValue(selected.id, selected.valueDiv)
this.playSound(confirm ? "se_don" : "se_cancel")
this.latencySettings.style.display = ""
this.mode = "settings"
}
numberAdjust(selected, add){
var selectedItem = this.items[this.selected]
var mul = Math.pow(10, selected.fixedPoint || 0)
selectedItem.value += add * ("step" in selected ? selected.step : 1)
if("max" in selected && selectedItem.value > selected.max * mul){
selectedItem.value = selected.max * mul
}else if("min" in selected && selectedItem.value < selected.min * mul){
selectedItem.value = selected.min * mul
}else{
this.playSound("se_ka")
}
var valueText = Intl.NumberFormat(strings.intl, selected.sign ? {
signDisplay: "always"
} : undefined).format(selectedItem.value / mul)
if(selected.format || selected.format_lang){
valueText = this.getLocalTitle(selected.format, selected.format_lang).replace("%s", valueText)
}
selectedItem.valueText.data = valueText
}
numberBack(selected){
this.mode = "settings"
selected.settingBox.style.animation = ""
selected.valueDiv.classList.remove("selected")
var current = this.settingsItems[selected.id]
var promise
var mul = Math.pow(10, selected.fixedPoint || 0)
var value = selected.value / mul
if(current.setItem){
promise = current.setItem(value)
}else{
settings.setItem(selected.id, value)
}
(promise || Promise.resolve()).then(() => {
this.getValue(selected.id, selected.valueText)
})
}
addMs(input){
var split = strings.calibration.ms.split("%s")
var index = 0
var output = ""
var inputStrings = [(input > 0 ? "+" : "") + input.toString()]
split.forEach((string, i) => {
if(i !== 0){
output += inputStrings[index++]
}
output += string
})
return output
}
defaultSettings(){
if(this.customSettings){
plugins.unloadImported()
this.clean(true)
this.playSound("se_don")
return setTimeout(() => this.restart(), 500)
}
if(this.mode === "keyboard"){
this.keyboardBack(this.items[this.selected])
}
for(var i in this.settingsItems){
settings.setItem(i, null)
}
this.setLang(allStrings[settings.getItem("language")])
this.keyboard.update()
pageEvents.setKbd()
this.latencyItems.forEach(item => {
if(item.id === "audio" || item.id === "video" || item.id === "drumSounds"){
this.latencyGetValue(item.id, item.valueText)
}
})
this.drumSounds = settings.getItem("latency").drumSounds
this.playSound("se_don")
}
browseChange(event){
this.locked = true
var files = []
for(var i = 0; i < event.target.files.length; i++){
files.push(new LocalFile(event.target.files[i]))
}
var customSongs = new CustomSongs(this.touchEnabled, true)
customSongs.importLocal(files).then(() => {
this.clean(true)
return this.restart()
}).catch(e => {
if(e){
var message = e.message
if(e.name === "nosongs"){
message = strings.plugins.noPlugins
}
if(message){
alert(message)
}
}
this.locked = false
this.browse.form.reset()
return Promise.resolve()
})
}
onEnd(){
if(this.mode === "number"){
this.numberBack(this.items[this.selected])
}
this.clean()
this.playSound("se_don")
setTimeout(() => {
if(this.tutorial && !this.touched){
new Tutorial(false, this.songId)
}else{
try{
localStorage.setItem("tutorial", "true")
}catch(e){}
new SongSelect(this.tutorial ? false : this.customSettings ? "plugins" : "settings", false, this.touched, this.songId)
}
}, 500)
}
restart(){
if(this.mode === "number"){
this.numberBack(this.items[this.selected])
}
return new SettingsView(this.touchEnabled, this.tutorial, this.songId, undefined, this.customSettings ? plugins.getSettings() : undefined, true)
}
getLocalTitle(title, titleLang){
if(titleLang){
for(var id in titleLang){
if(id === strings.id && titleLang[id]){
return titleLang[id]
}
}
}
return title
}
setLang(lang){
if(lang){
settings.setLang(lang)
}
if(failedTests.length !== 0){
showUnsupported(strings)
}
for(var i in this.items){
var item = this.items[i]
if(item.valueDiv){
if(item.name || item.name_lang){
var name = this.getLocalTitle(item.name, item.name_lang)
}else{
var name = strings.settings[item.id].name
}
this.setAltText(item.nameDiv, name)
if(item.description || item.description_lang){
item.settingBox.title = this.getLocalTitle(item.description, item.description_lang) || ""
}
this.getValue(item.id, item.valueDiv)
}
}
for(var i in this.latencyItems){
var current = this.latencyItems[i]
if(current.nameDiv){
this.setAltText(current.nameDiv, strings.settings.latency[current.id])
}
if(current.valueText){
this.latencyGetValue(current.id, current.valueText)
}
}
this.setStrings()
}
setStrings(){
this.setAltText(this.viewTitle, this.customSettings ? strings.plugins.title : strings.gameSettings)
this.setAltText(this.endButton, strings.settings.ok)
if(this.customSettings){
this.browseText.data = strings.plugins.browse
this.browseButton.setAttribute("alt", strings.plugins.browse)
}else{
this.setAltText(this.gamepadTitle, strings.settings.gamepadLayout.name)
this.setAltText(this.gamepadEndButton, strings.settings.ok)
this.setAltText(this.latencyTitle, strings.settings.latency.name)
this.setAltText(this.latencyDefaultButton, strings.settings.default)
this.setAltText(this.latencyEndButton, strings.settings.ok)
}
if(this.showDefault){
this.setAltText(this.defaultButton, this.customSettings ? strings.plugins.unloadAll : strings.settings.default)
}
}
setAltText(element, text){
element.innerText = text
element.setAttribute("alt", text)
}
mod(length, index){
return ((index % length) + length) % length
}
playSound(id, time){
if(!this.drumSounds && (id === "se_don" || id === "se_ka" || id === "se_cancel")){
return
}
var ms = Date.now() + (time || 0) * 1000
if(!(id in this.playedSounds) || ms > this.playedSounds[id] + 30){
assets.sounds[id].play(time)
this.playedSounds[id] = ms
}
}
redraw(){
if(!this.redrawRunning){
return
}
requestAnimationFrame(this.redrawBind)
var ms = this.getMS()
for(var key in this.pressedKeys){
if(this.pressedKeys[key]){
if(ms >= this.pressedKeys[key] + 50){
this.keyPressed(true, key, null, true)
this.pressedKeys[key] = ms
}
}
}
}
getMS(){
return Date.now()
}
clean(noSoundStop){
this.redrawRunning = false
this.keyboard.clean()
this.gamepad.clean()
if(!noSoundStop){
assets.sounds["bgm_settings"].stop()
}
pageEvents.remove(window, ["mouseup", "touchstart", "touchmove", "touchend", "blur"], this.windowSymbol)
if(this.customSettings){
pageEvents.remove(window, "language-change", this.windowSymbol)
}
for(var i in this.items){
this.removeTouchEnd(this.items[i].settingBox)
}
for(var i in this.latencyItems){
this.removeTouch(this.latencyItems[i].settingBox)
if(this.latencyItems[i].buttonMinus){
this.removeTouch(this.latencyItems[i].buttonMinus)
this.removeTouch(this.latencyItems[i].buttonPlus)
}
}
if(this.defaultButton){
delete this.defaultButton
}
if(this.customSettings){
pageEvents.remove(this.browse, "change")
delete this.browse
delete this.browseButton
delete this.browseText
}else{
this.removeTouch(this.gamepadSettings)
this.removeTouch(this.gamepadEndButton)
this.removeTouch(this.gamepadBox)
this.removeTouch(this.latencySettings)
this.removeTouch(this.latencyDefaultButton)
this.removeTouch(this.latencyEndButton)
}
delete this.windowSymbol
delete this.touchMove
delete this.viewOuter
delete this.touchEnd
delete this.tutorialTitle
delete this.endButton
delete this.items
delete this.gamepadSettings
delete this.gamepadTitle
delete this.gamepadEndButton
delete this.gamepadBox
delete this.gamepadButtons
delete this.gamepadValue
delete this.latencyItems
delete this.latencySettings
delete this.latencyTitle
delete this.latencyDefaultButton
delete this.latencyEndButton
if(this.resolution !== settings.getItem("resolution")){
for(var i in assets.image){
if(i === "touch_drum" || i.startsWith("bg_song_") || i.startsWith("bg_stage_") || i.startsWith("bg_don_") || i.startsWith("results_")){
var img = assets.image[i]
URL.revokeObjectURL(img.src)
if(img.parentNode){
img.parentNode.removeChild(img)
}
delete assets.image[i]
}
}
}
}
}