Implement Cache Songs, Cancel Loading, Admin Panel, Custom Category, Localization
This commit is contained in:
@@ -46,22 +46,22 @@ class RemoteFile{
|
||||
}
|
||||
}
|
||||
}
|
||||
arrayBuffer(){
|
||||
arrayBuffer(cancellationToken){
|
||||
return loader.ajax(this.url, request => {
|
||||
request.responseType = "arraybuffer"
|
||||
})
|
||||
}, false, cancellationToken)
|
||||
}
|
||||
read(encoding){
|
||||
read(encoding, cancellationToken){
|
||||
if(encoding){
|
||||
return this.blob().then(blob => readFile(blob, false, encoding))
|
||||
return this.blob(cancellationToken).then(blob => readFile(blob, false, encoding))
|
||||
}else{
|
||||
return loader.ajax(this.url)
|
||||
return loader.ajax(this.url, null, false, cancellationToken)
|
||||
}
|
||||
}
|
||||
blob(){
|
||||
blob(cancellationToken){
|
||||
return loader.ajax(this.url, request => {
|
||||
request.responseType = "blob"
|
||||
})
|
||||
}, false, cancellationToken)
|
||||
}
|
||||
}
|
||||
class LocalFile{
|
||||
|
||||
@@ -38,6 +38,7 @@ var assets = {
|
||||
"customsongs.js",
|
||||
"abstractfile.js",
|
||||
"idb.js",
|
||||
"songcacher.js",
|
||||
"plugins.js",
|
||||
"search.js"
|
||||
],
|
||||
|
||||
@@ -537,9 +537,12 @@ class Loader{
|
||||
}
|
||||
return css.join("\n")
|
||||
}
|
||||
ajax(url, customRequest, customResponse){
|
||||
ajax(url, customRequest, customResponse, cancellationToken){
|
||||
var request = new XMLHttpRequest()
|
||||
request.open("GET", url)
|
||||
if(cancellationToken){
|
||||
cancellationToken.cancel = () => request.abort()
|
||||
}
|
||||
var promise = pageEvents.load(request)
|
||||
if(!customResponse){
|
||||
promise = promise.then(() => {
|
||||
|
||||
@@ -57,6 +57,11 @@ class Settings{
|
||||
showLyrics: {
|
||||
type: "toggle",
|
||||
default: true
|
||||
},
|
||||
cacheSongs: {
|
||||
type: "select",
|
||||
options: ["none", "cacheAll", "cacheCategory", "cacheSingle"],
|
||||
default: "none"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,7 +601,13 @@ class SettingsView{
|
||||
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]){
|
||||
if(name === "cacheSongs"){
|
||||
if(value === "none"){
|
||||
value = strings.none
|
||||
}else{
|
||||
value = strings.cache[value]
|
||||
}
|
||||
}else 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]
|
||||
@@ -674,6 +685,22 @@ class SettingsView{
|
||||
}
|
||||
if(current.type === "language" || current.type === "select"){
|
||||
value = current.options[this.mod(current.options.length, current.options.indexOf(value) + 1)]
|
||||
if(name === "cacheSongs" && value !== "none"){
|
||||
var cacher = new SongCacher()
|
||||
if(value === "cacheAll"){
|
||||
cacher.cacheAll()
|
||||
}else if(value === "cacheCategory"){
|
||||
var song = assets.songs.find(s => s.id === this.songId)
|
||||
if(song){
|
||||
cacher.cacheCategory(song.category_id)
|
||||
}
|
||||
}else if(value === "cacheSingle"){
|
||||
if(this.songId){
|
||||
cacher.cacheSong(this.songId)
|
||||
}
|
||||
}
|
||||
value = "none"
|
||||
}
|
||||
}else if(current.type === "toggle"){
|
||||
value = !value
|
||||
}else if(current.type === "keyboard"){
|
||||
|
||||
82
public/src/js/songcacher.js
Normal file
82
public/src/js/songcacher.js
Normal file
@@ -0,0 +1,82 @@
|
||||
class SongCacher {
|
||||
constructor() {
|
||||
this.cancelled = false
|
||||
}
|
||||
|
||||
cacheAll() {
|
||||
this.run(assets.songs)
|
||||
}
|
||||
|
||||
cacheCategory(categoryId) {
|
||||
var songs = assets.songs.filter(song => song.category_id === categoryId)
|
||||
this.run(songs)
|
||||
}
|
||||
|
||||
cacheSong(songId) {
|
||||
var songs = assets.songs.filter(song => song.id === songId)
|
||||
this.run(songs)
|
||||
}
|
||||
|
||||
async run(songs) {
|
||||
if (!confirm(strings.cache.cacheConfirm)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.cancelled = false
|
||||
var total = songs.length
|
||||
var current = 0
|
||||
|
||||
var loaderDiv = document.createElement("div")
|
||||
loaderDiv.style.position = "fixed"
|
||||
loaderDiv.style.top = "0"
|
||||
loaderDiv.style.left = "0"
|
||||
loaderDiv.style.width = "100%"
|
||||
loaderDiv.style.height = "100%"
|
||||
loaderDiv.style.backgroundColor = "rgba(0,0,0,0.8)"
|
||||
loaderDiv.style.zIndex = "10000"
|
||||
loaderDiv.style.color = "white"
|
||||
loaderDiv.style.display = "flex"
|
||||
loaderDiv.style.justifyContent = "center"
|
||||
loaderDiv.style.alignItems = "center"
|
||||
loaderDiv.style.fontSize = "24px"
|
||||
loaderDiv.style.flexDirection = "column"
|
||||
|
||||
var text = document.createElement("div")
|
||||
loaderDiv.appendChild(text)
|
||||
|
||||
var cancelBtn = document.createElement("button")
|
||||
cancelBtn.innerText = strings.cancel
|
||||
cancelBtn.style.marginTop = "20px"
|
||||
cancelBtn.style.padding = "10px 20px"
|
||||
cancelBtn.style.fontSize = "20px"
|
||||
cancelBtn.onclick = () => {
|
||||
this.cancelled = true
|
||||
document.body.removeChild(loaderDiv)
|
||||
}
|
||||
loaderDiv.appendChild(cancelBtn)
|
||||
|
||||
document.body.appendChild(loaderDiv)
|
||||
|
||||
for (let song of songs) {
|
||||
if (this.cancelled) break
|
||||
current++
|
||||
text.innerText = strings.cache.cacheProgress.replace("%s", current).replace("%s", total) + "\n" + song.title
|
||||
|
||||
try {
|
||||
if (song.music) {
|
||||
await song.music.blob()
|
||||
}
|
||||
if (song.chart && song.chart.read) {
|
||||
await song.chart.read()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.cancelled) {
|
||||
document.body.removeChild(loaderDiv)
|
||||
alert(strings.cache.cacheComplete)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2841,6 +2841,13 @@ class SongSelect{
|
||||
var currentId = this.previewId
|
||||
this.previewing = this.selectedSong
|
||||
}
|
||||
|
||||
if(this.previewCancellationToken){
|
||||
this.previewCancellationToken.cancel()
|
||||
}
|
||||
this.previewCancellationToken = {}
|
||||
var cancellationToken = this.previewCancellationToken
|
||||
|
||||
var songObj = this.previewList.find(song => song && song.id === id)
|
||||
|
||||
if(songObj){
|
||||
@@ -2853,19 +2860,19 @@ class SongSelect{
|
||||
songObj = {id: id}
|
||||
if(currentSong.previewMusic){
|
||||
songObj.preview_time = 0
|
||||
var promise = snd.previewGain.load(currentSong.previewMusic).catch(() => {
|
||||
var promise = snd.previewGain.load(currentSong.previewMusic, cancellationToken).catch(() => {
|
||||
songObj.preview_time = prvTime
|
||||
return snd.previewGain.load(currentSong.music)
|
||||
return snd.previewGain.load(currentSong.music, cancellationToken)
|
||||
})
|
||||
}else if(currentSong.unloaded){
|
||||
var promise = this.getUnloaded(this.selectedSong, songObj, currentId)
|
||||
var promise = this.getUnloaded(this.selectedSong, songObj, currentId, cancellationToken)
|
||||
}else if(currentSong.sound){
|
||||
songObj.preview_time = prvTime
|
||||
currentSong.sound.gain = snd.previewGain
|
||||
var promise = Promise.resolve(currentSong.sound)
|
||||
}else if(currentSong.music !== "muted"){
|
||||
songObj.preview_time = prvTime
|
||||
var promise = snd.previewGain.load(currentSong.music)
|
||||
var promise = snd.previewGain.load(currentSong.music, cancellationToken)
|
||||
}else{
|
||||
return
|
||||
}
|
||||
@@ -2919,11 +2926,11 @@ class SongSelect{
|
||||
snd.musicGain.fadeOut(0.4)
|
||||
}
|
||||
}
|
||||
getUnloaded(selectedSong, songObj, currentId){
|
||||
getUnloaded(selectedSong, songObj, currentId, cancellationToken){
|
||||
var currentSong = this.songs[selectedSong]
|
||||
var file = currentSong.chart
|
||||
var importSongs = new ImportSongs(false, assets.otherFiles)
|
||||
return file.read(currentSong.type === "tja" ? "utf-8" : "").then(data => {
|
||||
return file.read(currentSong.type === "tja" ? "utf-8" : "", cancellationToken).then(data => {
|
||||
currentSong.chart = new CachedFile(data, file)
|
||||
return importSongs[currentSong.type === "tja" ? "addTja" : "addOsu"]({
|
||||
file: currentSong.chart,
|
||||
@@ -2940,7 +2947,7 @@ class SongSelect{
|
||||
this.songs[selectedSong] = this.addSong(imported)
|
||||
this.state.moveMS = this.getMS() - this.songSelecting.speed * this.songSelecting.resize
|
||||
if(imported.music && currentId === this.previewId){
|
||||
return snd.previewGain.load(imported.music).then(sound => {
|
||||
return snd.previewGain.load(imported.music, cancellationToken).then(sound => {
|
||||
imported.sound = sound
|
||||
this.songs[selectedSong].sound = sound
|
||||
return sound.copy()
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
pageEvents.add(window, ["click", "touchend", "keypress"], this.pageClicked.bind(this))
|
||||
this.gainList = []
|
||||
}
|
||||
load(file, gain){
|
||||
load(file, gain, cancellationToken){
|
||||
var decoder = file.name.endsWith(".ogg") ? this.oggDecoder : this.audioDecoder
|
||||
return file.arrayBuffer().then(response => {
|
||||
return file.arrayBuffer(cancellationToken).then(response => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return decoder(response, resolve, reject)
|
||||
}).catch(error => Promise.reject([error, file.url]))
|
||||
@@ -89,8 +89,8 @@ class SoundGain{
|
||||
}
|
||||
this.setVolume(1)
|
||||
}
|
||||
load(url){
|
||||
return this.soundBuffer.load(url, this)
|
||||
load(url, cancellationToken){
|
||||
return this.soundBuffer.load(url, this, cancellationToken)
|
||||
}
|
||||
convertTime(time, absolute){
|
||||
return this.soundBuffer.convertTime(time, absolute)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var languageList = ["ja", "en", "cn", "tw", "ko"]
|
||||
var languageList = ["ja", "en", "cn", "tw", "ko"]
|
||||
var translations = {
|
||||
name: {
|
||||
ja: "日本語",
|
||||
@@ -1235,6 +1235,54 @@ var translations = {
|
||||
ko: "제작자:"
|
||||
}
|
||||
},
|
||||
cache: {
|
||||
cacheSongs: {
|
||||
ja: "曲をキャッシュ",
|
||||
en: "Cache Songs",
|
||||
cn: "缓存歌曲",
|
||||
tw: "緩存歌曲",
|
||||
ko: "곡 캐시"
|
||||
},
|
||||
cacheAll: {
|
||||
ja: "すべてのカテゴリ",
|
||||
en: "All Categories",
|
||||
cn: "全部分类",
|
||||
tw: "全部分類",
|
||||
ko: "모든 카테고리"
|
||||
},
|
||||
cacheCategory: {
|
||||
ja: "現在のカテゴリ",
|
||||
en: "Current Category",
|
||||
cn: "对应分类",
|
||||
tw: "對應分類",
|
||||
ko: "현재 카테고리"
|
||||
},
|
||||
cacheSingle: {
|
||||
ja: "単一の曲",
|
||||
en: "Single Song",
|
||||
cn: "单独歌曲",
|
||||
tw: "單獨歌曲",
|
||||
ko: "개별 곡"
|
||||
},
|
||||
cacheConfirm: {
|
||||
en: "This will download a lot of data. Continue?",
|
||||
cn: "这将下载大量数据,确定吗?"
|
||||
},
|
||||
cacheProgress: {
|
||||
en: "Caching... %s / %s",
|
||||
cn: "缓存中... %s / %s"
|
||||
},
|
||||
cacheComplete: {
|
||||
en: "Caching complete!",
|
||||
cn: "缓存完成!"
|
||||
}
|
||||
},
|
||||
admin: {
|
||||
login: {
|
||||
en: "Admin Login",
|
||||
cn: "管理员登录"
|
||||
}
|
||||
},
|
||||
withLyrics: {
|
||||
ja: "歌詞あり",
|
||||
en: "With lyrics",
|
||||
|
||||
Reference in New Issue
Block a user