Implement Cache Songs, Cancel Loading, Admin Panel, Custom Category, Localization

This commit is contained in:
2025-12-28 11:54:47 +08:00
parent 92c1261f6f
commit ae4a0f823e
13 changed files with 375 additions and 27 deletions

View File

@@ -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{

View File

@@ -38,6 +38,7 @@ var assets = {
"customsongs.js",
"abstractfile.js",
"idb.js",
"songcacher.js",
"plugins.js",
"search.js"
],

View File

@@ -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(() => {

View File

@@ -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"){

View 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)
}
}
}

View File

@@ -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()

View File

@@ -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)

View File

@@ -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",