refactor: remove tjaf dependency; add local TJA parser
This commit is contained in:
587
public/src/js/loader.js
Normal file
587
public/src/js/loader.js
Normal file
@@ -0,0 +1,587 @@
|
||||
class Loader{
|
||||
constructor(...args){
|
||||
this.init(...args)
|
||||
}
|
||||
init(callback){
|
||||
this.callback = callback
|
||||
this.loadedAssets = 0
|
||||
this.assetsDiv = document.getElementById("assets")
|
||||
this.screen = document.getElementById("screen")
|
||||
this.startTime = Date.now()
|
||||
this.errorMessages = []
|
||||
this.songSearchGradient = "linear-gradient(to top, rgba(245, 246, 252, 0.08), #ff5963), "
|
||||
|
||||
var promises = []
|
||||
|
||||
promises.push(this.ajax("src/views/loader.html").then(page => {
|
||||
this.screen.innerHTML = page
|
||||
}))
|
||||
|
||||
promises.push(this.ajax("api/config").then(conf => {
|
||||
gameConfig = JSON.parse(conf)
|
||||
}))
|
||||
|
||||
Promise.all(promises).then(this.run.bind(this))
|
||||
}
|
||||
run(){
|
||||
this.promises = []
|
||||
this.loaderDiv = document.querySelector("#loader")
|
||||
this.loaderPercentage = document.querySelector("#loader .percentage")
|
||||
this.loaderProgress = document.querySelector("#loader .progress")
|
||||
|
||||
this.queryString = gameConfig._version.commit_short ? "?" + gameConfig._version.commit_short : ""
|
||||
|
||||
if(gameConfig.custom_js){
|
||||
this.addPromise(this.loadScript(gameConfig.custom_js), gameConfig.custom_js)
|
||||
}
|
||||
var oggSupport = new Audio().canPlayType("audio/ogg;codecs=vorbis")
|
||||
if(!oggSupport){
|
||||
assets.js.push("lib/oggmented-wasm.js")
|
||||
}
|
||||
assets.js.forEach(name => {
|
||||
this.addPromise(this.loadScript("src/js/" + name), "src/js/" + name)
|
||||
})
|
||||
|
||||
var pageVersion = versionLink.href
|
||||
var index = pageVersion.lastIndexOf("/")
|
||||
if(index !== -1){
|
||||
pageVersion = pageVersion.slice(index + 1)
|
||||
}
|
||||
this.addPromise(new Promise((resolve, reject) => {
|
||||
if(
|
||||
versionLink.href !== gameConfig._version.url &&
|
||||
gameConfig._version.commit &&
|
||||
versionLink.href.indexOf(gameConfig._version.commit) === -1
|
||||
){
|
||||
reject("Version on the page and config does not match\n(page: " + pageVersion + ",\nconfig: "+ gameConfig._version.commit + ")")
|
||||
}
|
||||
var cssCount = document.styleSheets.length + assets.css.length
|
||||
assets.css.forEach(name => {
|
||||
var stylesheet = document.createElement("link")
|
||||
stylesheet.rel = "stylesheet"
|
||||
stylesheet.href = "src/css/" + name + this.queryString
|
||||
document.head.appendChild(stylesheet)
|
||||
})
|
||||
var checkStyles = () => {
|
||||
if(document.styleSheets.length >= cssCount){
|
||||
resolve()
|
||||
clearInterval(interval)
|
||||
}
|
||||
}
|
||||
var interval = setInterval(checkStyles, 100)
|
||||
checkStyles()
|
||||
}))
|
||||
|
||||
for(var name in assets.fonts){
|
||||
var url = gameConfig.assets_baseurl + "fonts/" + assets.fonts[name]
|
||||
this.addPromise(new FontFace(name, "url('" + url + "')").load().then(font => {
|
||||
document.fonts.add(font)
|
||||
}), url)
|
||||
}
|
||||
|
||||
assets.img.forEach(name => {
|
||||
var id = this.getFilename(name)
|
||||
var image = document.createElement("img")
|
||||
image.crossOrigin = "anonymous"
|
||||
var url = gameConfig.assets_baseurl + "img/" + name
|
||||
this.addPromise(pageEvents.load(image), url)
|
||||
image.id = name
|
||||
image.src = url
|
||||
this.assetsDiv.appendChild(image)
|
||||
assets.image[id] = image
|
||||
})
|
||||
|
||||
var css = []
|
||||
for(let selector in assets.cssBackground){
|
||||
let name = assets.cssBackground[selector]
|
||||
var url = gameConfig.assets_baseurl + "img/" + name
|
||||
this.addPromise(loader.ajax(url, request => {
|
||||
request.responseType = "blob"
|
||||
}).then(blob => {
|
||||
var id = this.getFilename(name)
|
||||
var image = document.createElement("img")
|
||||
let blobUrl = URL.createObjectURL(blob)
|
||||
var promise = pageEvents.load(image).then(() => {
|
||||
var gradient = ""
|
||||
if(selector === ".pattern-bg"){
|
||||
loader.screen.style.backgroundImage = "url(\"" + blobUrl + "\")"
|
||||
}else if(selector === "#song-search"){
|
||||
gradient = this.songSearchGradient
|
||||
}
|
||||
css.push(this.cssRuleset({
|
||||
[selector]: {
|
||||
"background-image": gradient + "url(\"" + blobUrl + "\")"
|
||||
}
|
||||
}))
|
||||
})
|
||||
image.id = name
|
||||
image.src = blobUrl
|
||||
this.assetsDiv.appendChild(image)
|
||||
assets.image[id] = image
|
||||
return promise
|
||||
}), url)
|
||||
}
|
||||
|
||||
assets.views.forEach(name => {
|
||||
var id = this.getFilename(name)
|
||||
var url = "src/views/" + name + this.queryString
|
||||
this.addPromise(this.ajax(url).then(page => {
|
||||
assets.pages[id] = page
|
||||
}), url)
|
||||
})
|
||||
|
||||
this.addPromise(this.ajax("api/categories").then(cats => {
|
||||
assets.categories = JSON.parse(cats)
|
||||
assets.categories.forEach(cat => {
|
||||
if(cat.song_skin){
|
||||
cat.songSkin = cat.song_skin //rename the song_skin property and add category title to categories array
|
||||
delete cat.song_skin
|
||||
cat.songSkin.infoFill = cat.songSkin.info_fill
|
||||
delete cat.songSkin.info_fill
|
||||
}
|
||||
})
|
||||
|
||||
assets.categories.push({
|
||||
title: "default",
|
||||
songSkin: {
|
||||
background: "#ececec",
|
||||
border: ["#fbfbfb", "#8b8b8b"],
|
||||
outline: "#656565",
|
||||
infoFill: "#656565"
|
||||
}
|
||||
})
|
||||
}), "api/categories")
|
||||
|
||||
var url = gameConfig.assets_baseurl + "img/vectors.json" + this.queryString
|
||||
this.addPromise(this.ajax(url).then(response => {
|
||||
vectors = JSON.parse(response)
|
||||
}), url)
|
||||
|
||||
this.afterJSCount =
|
||||
[
|
||||
"api/songs",
|
||||
"blurPerformance",
|
||||
"categories"
|
||||
].length +
|
||||
assets.audioSfx.length +
|
||||
assets.audioMusic.length +
|
||||
assets.audioSfxLR.length +
|
||||
assets.audioSfxLoud.length +
|
||||
(gameConfig.accounts ? 1 : 0)
|
||||
|
||||
Promise.all(this.promises).then(() => {
|
||||
if(this.error){
|
||||
return
|
||||
}
|
||||
|
||||
var style = document.createElement("style")
|
||||
style.appendChild(document.createTextNode(css.join("\n")))
|
||||
document.head.appendChild(style)
|
||||
|
||||
this.addPromise(this.ajax("api/songs").then(songs => {
|
||||
songs = JSON.parse(songs)
|
||||
songs.forEach(song => {
|
||||
var directory = gameConfig.songs_baseurl + song.id + "/"
|
||||
var songExt = song.music_type ? song.music_type : "mp3"
|
||||
song.music = new RemoteFile(directory + "main." + songExt)
|
||||
if(song.type === "tja"){
|
||||
song.chart = new RemoteFile(directory + "main.tja")
|
||||
}else{
|
||||
song.chart = {separateDiff: true}
|
||||
for(var diff in song.courses){
|
||||
if(song.courses[diff]){
|
||||
song.chart[diff] = new RemoteFile(directory + diff + ".osu")
|
||||
}
|
||||
}
|
||||
}
|
||||
if(song.lyrics){
|
||||
song.lyricsFile = new RemoteFile(directory + "main.vtt")
|
||||
}
|
||||
if(song.preview > 0){
|
||||
song.previewMusic = new RemoteFile(directory + "preview." + gameConfig.preview_type)
|
||||
}
|
||||
})
|
||||
assets.songsDefault = songs
|
||||
assets.songs = assets.songsDefault
|
||||
}), "api/songs")
|
||||
|
||||
var categoryPromises = []
|
||||
assets.categories //load category backgrounds to DOM
|
||||
.filter(cat => cat.songSkin && cat.songSkin.bg_img)
|
||||
.forEach(cat => {
|
||||
let name = cat.songSkin.bg_img
|
||||
var url = gameConfig.assets_baseurl + "img/" + name
|
||||
categoryPromises.push(loader.ajax(url, request => {
|
||||
request.responseType = "blob"
|
||||
}).then(blob => {
|
||||
var id = this.getFilename(name)
|
||||
var image = document.createElement("img")
|
||||
let blobUrl = URL.createObjectURL(blob)
|
||||
var promise = pageEvents.load(image)
|
||||
image.id = name
|
||||
image.src = blobUrl
|
||||
this.assetsDiv.appendChild(image)
|
||||
assets.image[id] = image
|
||||
return promise
|
||||
}).catch(response => {
|
||||
return this.errorMsg(response, url)
|
||||
}))
|
||||
})
|
||||
this.addPromise(Promise.all(categoryPromises))
|
||||
|
||||
snd.buffer = new SoundBuffer()
|
||||
if(!oggSupport){
|
||||
snd.buffer.oggDecoder = snd.buffer.fallbackDecoder
|
||||
}
|
||||
snd.musicGain = snd.buffer.createGain()
|
||||
snd.sfxGain = snd.buffer.createGain()
|
||||
snd.previewGain = snd.buffer.createGain()
|
||||
snd.sfxGainL = snd.buffer.createGain("left")
|
||||
snd.sfxGainR = snd.buffer.createGain("right")
|
||||
snd.sfxLoudGain = snd.buffer.createGain()
|
||||
snd.buffer.setCrossfade(
|
||||
[snd.musicGain, snd.previewGain],
|
||||
[snd.sfxGain, snd.sfxGainL, snd.sfxGainR],
|
||||
0.5
|
||||
)
|
||||
snd.sfxLoudGain.setVolume(1.2)
|
||||
snd.buffer.saveSettings()
|
||||
|
||||
this.afterJSCount = 0
|
||||
|
||||
assets.audioSfx.forEach(name => {
|
||||
this.addPromise(this.loadSound(name, snd.sfxGain), this.soundUrl(name))
|
||||
})
|
||||
assets.audioMusic.forEach(name => {
|
||||
this.addPromise(this.loadSound(name, snd.musicGain), this.soundUrl(name))
|
||||
})
|
||||
assets.audioSfxLR.forEach(name => {
|
||||
this.addPromise(this.loadSound(name, snd.sfxGain).then(sound => {
|
||||
var id = this.getFilename(name)
|
||||
assets.sounds[id + "_p1"] = assets.sounds[id].copy(snd.sfxGainL)
|
||||
assets.sounds[id + "_p2"] = assets.sounds[id].copy(snd.sfxGainR)
|
||||
}), this.soundUrl(name))
|
||||
})
|
||||
assets.audioSfxLoud.forEach(name => {
|
||||
this.addPromise(this.loadSound(name, snd.sfxLoudGain), this.soundUrl(name))
|
||||
})
|
||||
|
||||
this.canvasTest = new CanvasTest()
|
||||
this.addPromise(this.canvasTest.blurPerformance().then(result => {
|
||||
perf.blur = result
|
||||
if(result > 1000 / 50){
|
||||
// Less than 50 fps with blur enabled
|
||||
disableBlur = true
|
||||
}
|
||||
}), "blurPerformance")
|
||||
|
||||
if(gameConfig.accounts){
|
||||
this.addPromise(this.ajax("api/scores/get").then(response => {
|
||||
response = JSON.parse(response)
|
||||
if(response.status === "ok"){
|
||||
account.loggedIn = true
|
||||
account.username = response.username
|
||||
account.displayName = response.display_name
|
||||
account.don = response.don
|
||||
scoreStorage.load(response.scores)
|
||||
pageEvents.send("login", account.username)
|
||||
}
|
||||
}), "api/scores/get")
|
||||
}
|
||||
|
||||
settings = new Settings()
|
||||
pageEvents.setKbd()
|
||||
scoreStorage = new ScoreStorage()
|
||||
db = new IDB("taiko", "store")
|
||||
plugins = new Plugins()
|
||||
|
||||
if(localStorage.getItem("lastSearchQuery")){
|
||||
localStorage.removeItem("lastSearchQuery")
|
||||
}
|
||||
|
||||
Promise.all(this.promises).then(() => {
|
||||
if(this.error){
|
||||
return
|
||||
}
|
||||
if(!account.loggedIn){
|
||||
scoreStorage.load()
|
||||
}
|
||||
for(var i in assets.songsDefault){
|
||||
var song = assets.songsDefault[i]
|
||||
if(!song.hash){
|
||||
song.hash = song.title
|
||||
}
|
||||
scoreStorage.songTitles[song.title] = song.hash
|
||||
var score = scoreStorage.get(song.hash, false, true)
|
||||
if(score){
|
||||
score.title = song.title
|
||||
}
|
||||
}
|
||||
var promises = []
|
||||
|
||||
var readyEvent = "normal"
|
||||
var songId
|
||||
var hashLower = location.hash.toLowerCase()
|
||||
p2 = new P2Connection()
|
||||
if(hashLower.startsWith("#song=")){
|
||||
var number = parseInt(location.hash.slice(6))
|
||||
if(number > 0){
|
||||
songId = number
|
||||
readyEvent = "song-id"
|
||||
}
|
||||
}else if(location.hash.length === 6){
|
||||
p2.hashLock = true
|
||||
promises.push(new Promise(resolve => {
|
||||
p2.open()
|
||||
pageEvents.add(p2, "message", response => {
|
||||
if(response.type === "session"){
|
||||
pageEvents.send("session-start", "invited")
|
||||
readyEvent = "session-start"
|
||||
resolve()
|
||||
}else if(response.type === "gameend"){
|
||||
p2.hash("")
|
||||
p2.hashLock = false
|
||||
readyEvent = "session-expired"
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
p2.send("invite", {
|
||||
id: location.hash.slice(1).toLowerCase(),
|
||||
name: account.loggedIn ? account.displayName : null,
|
||||
don: account.loggedIn ? account.don : null
|
||||
})
|
||||
setTimeout(() => {
|
||||
if(p2.socket.readyState !== 1){
|
||||
p2.hash("")
|
||||
p2.hashLock = false
|
||||
resolve()
|
||||
}
|
||||
}, 10000)
|
||||
}).then(() => {
|
||||
pageEvents.remove(p2, "message")
|
||||
}))
|
||||
}else{
|
||||
p2.hash("")
|
||||
}
|
||||
|
||||
promises.push(this.canvasTest.drawAllImages().then(result => {
|
||||
perf.allImg = result
|
||||
}))
|
||||
|
||||
if(gameConfig.plugins){
|
||||
gameConfig.plugins.forEach(obj => {
|
||||
if(obj.url){
|
||||
var plugin = plugins.add(obj.url, {
|
||||
hide: obj.hide
|
||||
})
|
||||
if(plugin){
|
||||
plugin.loadErrors = true
|
||||
promises.push(plugin.load(true).then(() => {
|
||||
if(obj.start){
|
||||
return plugin.start(false, true)
|
||||
}
|
||||
}).catch(response => {
|
||||
return this.errorMsg(response, obj.url)
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
perf.load = Date.now() - this.startTime
|
||||
this.canvasTest.clean()
|
||||
this.clean()
|
||||
this.callback(songId)
|
||||
this.ready = true
|
||||
pageEvents.send("ready", readyEvent)
|
||||
}, e => this.errorMsg(e))
|
||||
}, e => this.errorMsg(e))
|
||||
})
|
||||
}
|
||||
addPromise(promise, url){
|
||||
this.promises.push(promise)
|
||||
promise.then(this.assetLoaded.bind(this), response => {
|
||||
return this.errorMsg(response, url)
|
||||
})
|
||||
}
|
||||
soundUrl(name){
|
||||
return gameConfig.assets_baseurl + "audio/" + name
|
||||
}
|
||||
loadSound(name, gain){
|
||||
var id = this.getFilename(name)
|
||||
return gain.load(new RemoteFile(this.soundUrl(name))).then(sound => {
|
||||
assets.sounds[id] = sound
|
||||
})
|
||||
}
|
||||
getFilename(name){
|
||||
return name.slice(0, name.lastIndexOf("."))
|
||||
}
|
||||
errorMsg(error, url){
|
||||
var rethrow
|
||||
if(url || error){
|
||||
if(typeof error === "object" && error.constructor === Error){
|
||||
rethrow = error
|
||||
error = error.stack || ""
|
||||
var index = error.indexOf("\n ")
|
||||
if(index !== -1){
|
||||
error = error.slice(0, index)
|
||||
}
|
||||
}else if(Array.isArray(error)){
|
||||
error = error[0]
|
||||
}
|
||||
if(url){
|
||||
error = (error ? error + ": " : "") + url
|
||||
}
|
||||
this.errorMessages.push(error)
|
||||
pageEvents.send("loader-error", url || error)
|
||||
}
|
||||
if(!this.error){
|
||||
this.error = true
|
||||
cancelTouch = false
|
||||
this.loaderDiv.classList.add("loaderError")
|
||||
if(typeof allStrings === "object"){
|
||||
var lang = localStorage.lang
|
||||
if(!lang){
|
||||
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])){
|
||||
lang = j
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!lang){
|
||||
lang = "en"
|
||||
}
|
||||
loader.screen.getElementsByClassName("view-content")[0].innerText = allStrings[lang] && allStrings[lang].errorOccured || allStrings.en.errorOccured
|
||||
}
|
||||
var loaderError = loader.screen.getElementsByClassName("loader-error-div")[0]
|
||||
loaderError.style.display = "flex"
|
||||
var diagTxt = loader.screen.getElementsByClassName("diag-txt")[0]
|
||||
var debugLink = loader.screen.getElementsByClassName("debug-link")[0]
|
||||
if(navigator.userAgent.indexOf("Android") >= 0){
|
||||
var iframe = document.createElement("iframe")
|
||||
diagTxt.appendChild(iframe)
|
||||
var body = iframe.contentWindow.document.body
|
||||
body.setAttribute("style", `
|
||||
font-family: monospace;
|
||||
margin: 2px 0 0 2px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
cursor: text;
|
||||
`)
|
||||
body.setAttribute("onblur", `
|
||||
getSelection().removeAllRanges()
|
||||
`)
|
||||
this.errorTxt = {
|
||||
element: body,
|
||||
method: "innerText"
|
||||
}
|
||||
}else{
|
||||
var textarea = document.createElement("textarea")
|
||||
textarea.readOnly = true
|
||||
diagTxt.appendChild(textarea)
|
||||
if(!this.touchEnabled){
|
||||
textarea.addEventListener("focus", () => {
|
||||
textarea.select()
|
||||
})
|
||||
textarea.addEventListener("blur", () => {
|
||||
getSelection().removeAllRanges()
|
||||
})
|
||||
}
|
||||
this.errorTxt = {
|
||||
element: textarea,
|
||||
method: "value"
|
||||
}
|
||||
}
|
||||
var show = () => {
|
||||
diagTxt.style.display = "block"
|
||||
debugLink.style.display = "none"
|
||||
}
|
||||
debugLink.addEventListener("click", show)
|
||||
debugLink.addEventListener("touchstart", show)
|
||||
this.clean(true)
|
||||
}
|
||||
var percentage = Math.floor(this.loadedAssets * 100 / (this.promises.length + this.afterJSCount))
|
||||
this.errorTxt.element[this.errorTxt.method] = "```\n" + this.errorMessages.join("\n") + "\nPercentage: " + percentage + "%\n```"
|
||||
if(rethrow || error){
|
||||
console.error(rethrow || error)
|
||||
}
|
||||
return Promise.reject()
|
||||
}
|
||||
assetLoaded(){
|
||||
if(!this.error){
|
||||
this.loadedAssets++
|
||||
var percentage = Math.floor(this.loadedAssets * 100 / (this.promises.length + this.afterJSCount))
|
||||
this.loaderProgress.style.width = percentage + "%"
|
||||
this.loaderPercentage.firstChild.data = percentage + "%"
|
||||
}
|
||||
}
|
||||
changePage(name, patternBg){
|
||||
this.screen.innerHTML = assets.pages[name]
|
||||
this.screen.classList[patternBg ? "add" : "remove"]("pattern-bg")
|
||||
}
|
||||
cssRuleset(rulesets){
|
||||
var css = []
|
||||
for(var selector in rulesets){
|
||||
var declarationsObj = rulesets[selector]
|
||||
var declarations = []
|
||||
for(var property in declarationsObj){
|
||||
var value = declarationsObj[property]
|
||||
declarations.push("\t" + property + ": " + value + ";")
|
||||
}
|
||||
css.push(selector + "{\n" + declarations.join("\n") + "\n}")
|
||||
}
|
||||
return css.join("\n")
|
||||
}
|
||||
ajax(url, customRequest, customResponse){
|
||||
var request = new XMLHttpRequest()
|
||||
request.open("GET", url)
|
||||
var promise = pageEvents.load(request)
|
||||
if(!customResponse){
|
||||
promise = promise.then(() => {
|
||||
if(request.status === 200){
|
||||
return request.response
|
||||
}else{
|
||||
return Promise.reject(`${url} (${request.status})`)
|
||||
}
|
||||
})
|
||||
}
|
||||
if(customRequest){
|
||||
customRequest(request)
|
||||
}
|
||||
request.send()
|
||||
return promise
|
||||
}
|
||||
loadScript(url){
|
||||
var script = document.createElement("script")
|
||||
var url = url + this.queryString
|
||||
var promise = pageEvents.load(script)
|
||||
script.src = url
|
||||
document.head.appendChild(script)
|
||||
return promise
|
||||
}
|
||||
getCsrfToken(){
|
||||
return this.ajax("api/csrftoken").then(response => {
|
||||
var json = JSON.parse(response)
|
||||
if(json.status === "ok"){
|
||||
return Promise.resolve(json.token)
|
||||
}else{
|
||||
return Promise.reject()
|
||||
}
|
||||
})
|
||||
}
|
||||
clean(error){
|
||||
delete this.loaderDiv
|
||||
delete this.loaderPercentage
|
||||
delete this.loaderProgress
|
||||
if(!error){
|
||||
delete this.promises
|
||||
delete this.errorText
|
||||
}
|
||||
pageEvents.remove(root, "touchstart")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user