- Add a "Browse..." button to the plugin menu
- Remove the "Unload All" button from the plugin menu if there are no imported plugins to unload
- Add a new search filter: random:yes
- Resolution settings now affects the results screen assets
- Pixelate more assets with lowest resolution setting
- Fix loading error message not appearing sometimes
- Remove img.css from img assets, the background selectors have been moved to assets.js
- Separate the search logic from SongSelect to its own js file
- Load all image assets with crossorigin=anonymous, this could allow making assets low resolution or programatically taking screenshots at a later time
- If EditFunction in a plugin tries to edit something that is not a function, it will give a better error message
- Disallow search engine bots from indexing images and adding a translate link, which cannot load the game
This commit is contained in:
KatieFrogs 2022-03-16 09:55:25 +03:00
parent 84b4bf00a8
commit e231ad1fcf
17 changed files with 993 additions and 755 deletions

View File

@ -1,39 +0,0 @@
.pattern-bg{
background-image: url("bg-pattern-1.png");
}
#song-select{
background-image: url("bg_genre_0.png");
}
#title-screen{
background-image: url("title-screen.png");
}
#loading-don{
background-image: url("dancing-don.gif");
}
#touch-full-btn{
background-image: url("touch_fullscreen.png");
}
#touch-pause-btn{
background-image: url("touch_pause.png");
}
.settings-outer{
background-image: url("bg_settings.png");
}
#gamepad-bg,
#gamepad-buttons{
background-image: url("settings_gamepad.png");
}
#song-search{
background: linear-gradient(to top, rgb(245 246 252 / 8%), #ff5963), url("bg_search.png");
background-size: auto, 3.12em;
background-position: -1.2em;
}
.song-search-result-course::before{
background-image: url("difficulty.png");
}
.song-search-result-crown{
background-image: url("crown.png");
}
.song-search-tip-error{
background-image: url("miss.png");
}

View File

@ -127,3 +127,15 @@
#song-lyrics rt{ #song-lyrics rt{
line-height: 1; line-height: 1;
} }
.pixelated #canvas,
.pixelated .donbg>div,
.pixelated #songbg>div,
.pixelated #song-stage,
.pixelated #touch-drum-img,
.pixelated #flowers1-in,
.pixelated #flowers2-in,
.pixelated #mikoshi-in,
.pixelated #tetsuo-in,
.pixelated #hana-in{
image-rendering: pixelated;
}

View File

@ -26,6 +26,8 @@
padding: 1em 1em 0 1em; padding: 1em 1em 0 1em;
z-index: 1; z-index: 1;
box-sizing: border-box; box-sizing: border-box;
background-size: auto, 3.12em;
background-position: 0%, -2%;
} }
#song-search-container.touch-enabled{ #song-search-container.touch-enabled{
@ -96,6 +98,7 @@
box-sizing: border-box; box-sizing: border-box;
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
border: 0.4em solid;
} }
.song-search-result:last-of-type { .song-search-result:last-of-type {
@ -133,6 +136,7 @@
content: attr(alt); content: attr(alt);
position: absolute; position: absolute;
z-index: -1; z-index: -1;
-webkit-text-stroke-width: 0.4em;
} }
.song-search-result-course { .song-search-result-course {

View File

@ -59,7 +59,9 @@ class RemoteFile{
} }
} }
blob(){ blob(){
return this.arrayBuffer().then(response => new Blob([response])) return loader.ajax(this.url, request => {
request.responseType = "blob"
})
} }
} }
class LocalFile{ class LocalFile{
@ -113,7 +115,7 @@ class GdriveFile{
this.url = gpicker.filesUrl + this.id + "?alt=media" this.url = gpicker.filesUrl + this.id + "?alt=media"
} }
arrayBuffer(){ arrayBuffer(){
return gpicker.downloadFile(this.id, true) return gpicker.downloadFile(this.id, "arraybuffer")
} }
read(encoding){ read(encoding){
if(encoding){ if(encoding){
@ -123,7 +125,7 @@ class GdriveFile{
} }
} }
blob(){ blob(){
return this.arrayBuffer().then(response => new Blob([response])) return gpicker.downloadFile(this.id, "blob")
} }
} }
class CachedFile{ class CachedFile{
@ -144,6 +146,6 @@ class CachedFile{
return this.arrayBuffer() return this.arrayBuffer()
} }
blob(){ blob(){
return this.arrayBuffer().then(response => new Blob([response])) return this.arrayBuffer()
} }
} }

View File

@ -38,7 +38,8 @@ var assets = {
"customsongs.js", "customsongs.js",
"abstractfile.js", "abstractfile.js",
"idb.js", "idb.js",
"plugins.js" "plugins.js",
"search.js"
], ],
"css": [ "css": [
"main.css", "main.css",
@ -50,20 +51,13 @@ var assets = {
"view.css", "view.css",
"search.css" "search.css"
], ],
"assetsCss": [
"img/img.css"
],
"img": [ "img": [
"title-screen.png",
"notes.png", "notes.png",
"notes_drumroll.png", "notes_drumroll.png",
"notes_hit.png", "notes_hit.png",
"notes_explosion.png", "notes_explosion.png",
"balloon.png", "balloon.png",
"taiko.png", "taiko.png",
"dancing-don.gif",
"bg-pattern-1.png",
"difficulty.png",
"don_anim_normal_a.png", "don_anim_normal_a.png",
"don_anim_normal_b1.png", "don_anim_normal_b1.png",
"don_anim_normal_b2.png", "don_anim_normal_b2.png",
@ -81,24 +75,26 @@ var assets = {
"don_anim_clear_b2.png", "don_anim_clear_b2.png",
"fire_anim.png", "fire_anim.png",
"fireworks_anim.png", "fireworks_anim.png",
"bg_genre_def.png",
"bg_score_p1.png", "bg_score_p1.png",
"bg_score_p2.png", "bg_score_p2.png",
"bg_settings.png",
"bg_pause.png", "bg_pause.png",
"badge_auto.png", "badge_auto.png",
"touch_pause.png", "mimizu.png"
"touch_fullscreen.png",
"mimizu.png",
"results_flowers.png",
"results_mikoshi.png",
"results_tetsuohana.png",
"results_tetsuohana2.png",
"settings_gamepad.png",
"crown.png",
"miss.png",
"bg_search.png"
], ],
"cssBackground": {
"#title-screen": "title-screen.png",
"#loading-don": "dancing-don.gif",
".pattern-bg": "bg-pattern-1.png",
".song-search-result-course::before": "difficulty.png",
"#song-select": "bg_genre_def.png",
".settings-outer": "bg_settings.png",
"#touch-pause-btn": "touch_pause.png",
"#touch-full-btn": "touch_fullscreen.png",
"#gamepad-bg, #gamepad-buttons": "settings_gamepad.png",
".song-search-result-crown": "crown.png",
".song-search-tip-error": "miss.png",
"#song-search": "bg_search.png"
},
"audioSfx": [ "audioSfx": [
"se_pause.ogg", "se_pause.ogg",
"se_calibration.ogg", "se_calibration.ogg",

View File

@ -2,7 +2,7 @@ class CustomSongs{
constructor(...args){ constructor(...args){
this.init(...args) this.init(...args)
} }
init(touchEnabled, noPage){ init(touchEnabled, noPage, noLoading){
this.loaderDiv = document.createElement("div") this.loaderDiv = document.createElement("div")
this.loaderDiv.innerHTML = assets.pages["loadsong"] this.loaderDiv.innerHTML = assets.pages["loadsong"]
var loadingText = this.loaderDiv.querySelector("#loading-text") var loadingText = this.loaderDiv.querySelector("#loading-text")
@ -13,6 +13,7 @@ class CustomSongs{
if(noPage){ if(noPage){
this.noPage = true this.noPage = true
this.noLoading = noLoading
return return
} }
@ -262,11 +263,13 @@ class CustomSongs{
var importSongs = new ImportSongs() var importSongs = new ImportSongs()
return importSongs.load(files).then(this.songsLoaded.bind(this), e => { return importSongs.load(files).then(this.songsLoaded.bind(this), e => {
this.browse.parentNode.reset() if(!this.noPage){
this.browse.form.reset()
}
this.locked = false this.locked = false
this.loading(false) this.loading(false)
if(e === "nosongs"){ if(e === "nosongs"){
this.showError(strings.customSongs.noSongs) this.showError(strings.customSongs.noSongs, "nosongs")
}else if(e !== "cancel"){ }else if(e !== "cancel"){
return Promise.reject(e) return Promise.reject(e)
} }
@ -308,7 +311,7 @@ class CustomSongs{
this.locked = false this.locked = false
this.loading(false) this.loading(false)
if(e === "nosongs"){ if(e === "nosongs"){
this.showError(strings.customSongs.noSongs) this.showError(strings.customSongs.noSongs, "nosongs")
}else if(e !== "cancel"){ }else if(e !== "cancel"){
return Promise.reject(e) return Promise.reject(e)
} }
@ -371,7 +374,7 @@ class CustomSongs{
open("privacy") open("privacy")
} }
loading(show){ loading(show){
if(this.noPage){ if(this.noLoading){
return return
} }
if(show){ if(show){
@ -387,14 +390,16 @@ class CustomSongs{
assets.customSongs = true assets.customSongs = true
assets.customSelected = this.noPage ? +localStorage.getItem("customSelected") : 0 assets.customSelected = this.noPage ? +localStorage.getItem("customSelected") : 0
} }
if(!this.noPage){ if(this.noPage){
pageEvents.send("import-songs", length)
}else{
assets.sounds["se_don"].play() assets.sounds["se_don"].play()
setTimeout(() => {
new SongSelect("customSongs", false, this.touchEnabled)
pageEvents.send("import-songs", length)
}, 500)
} }
this.clean() this.clean()
setTimeout(() => {
new SongSelect("customSongs", false, this.touchEnabled)
pageEvents.send("import-songs", length)
}, 500)
return songs && songs.length return songs && songs.length
} }
keyPressed(pressed, name){ keyPressed(pressed, name){
@ -474,10 +479,14 @@ class CustomSongs{
resolve() resolve()
}, 500)) }, 500))
} }
showError(text){ showError(text, errorName){
this.locked = false this.locked = false
this.loading(false) this.loading(false)
if(this.noPage || this.mode === "error"){ if(this.noPage){
var error = new Error(text)
error.name = errorName
throw error
}else if(this.mode === "error"){
return return
} }
this.mode = "error" this.mode = "error"

View File

@ -159,7 +159,7 @@ class Gpicker{
} }
clientCallback(tokenResponse){ clientCallback(tokenResponse){
this.tokenResponse = tokenResponse this.tokenResponse = tokenResponse
this.oauthToken = tokenResponse.access_token this.oauthToken = tokenResponse && tokenResponse.access_token
if(this.oauthToken && this.tokenResolve){ if(this.oauthToken && this.tokenResolve){
this.tokenResolve() this.tokenResolve()
} }
@ -220,12 +220,12 @@ class Gpicker{
.build() .build()
.setVisible(true) .setVisible(true)
} }
downloadFile(id, arrayBuffer, retry){ downloadFile(id, responseType, retry){
var url = this.filesUrl + id + "?alt=media" var url = this.filesUrl + id + "?alt=media"
return this.queue().then(this.getToken.bind(this)).then(() => return this.queue().then(this.getToken.bind(this)).then(() =>
loader.ajax(url, request => { loader.ajax(url, request => {
if(arrayBuffer){ if(responseType){
request.responseType = "arraybuffer" request.responseType = responseType
} }
request.setRequestHeader("Authorization", "Bearer " + this.oauthToken) request.setRequestHeader("Authorization", "Bearer " + this.oauthToken)
}, true).then(event => { }, true).then(event => {
@ -238,7 +238,7 @@ class Gpicker{
var e = response.error var e = response.error
if(e && e.errors[0].reason === "authError"){ if(e && e.errors[0].reason === "authError"){
delete this.oauthToken delete this.oauthToken
return this.downloadFile(id, arrayBuffer, true) return this.downloadFile(id, responseType, true)
}else{ }else{
return reject() return reject()
} }

View File

@ -61,12 +61,6 @@ class Loader{
stylesheet.href = "/src/css/" + name + this.queryString stylesheet.href = "/src/css/" + name + this.queryString
document.head.appendChild(stylesheet) document.head.appendChild(stylesheet)
}) })
assets.assetsCss.forEach(name => {
var stylesheet = document.createElement("link")
stylesheet.rel = "stylesheet"
stylesheet.href = gameConfig.assets_baseurl + name + this.queryString
document.head.appendChild(stylesheet)
})
var checkStyles = () => { var checkStyles = () => {
if(document.styleSheets.length >= cssCount){ if(document.styleSheets.length >= cssCount){
resolve() resolve()
@ -84,9 +78,10 @@ class Loader{
}), url) }), url)
} }
assets.img.forEach(name=>{ assets.img.forEach(name => {
var id = this.getFilename(name) var id = this.getFilename(name)
var image = document.createElement("img") var image = document.createElement("img")
image.crossOrigin = "anonymous"
var url = gameConfig.assets_baseurl + "img/" + name var url = gameConfig.assets_baseurl + "img/" + name
this.addPromise(pageEvents.load(image), url) this.addPromise(pageEvents.load(image), url)
image.id = name image.id = name
@ -95,6 +90,37 @@ class Loader{
assets.image[id] = 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 = "linear-gradient(to top, rgba(245, 246, 252, 0.08), #ff5963), "
}
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 => { assets.views.forEach(name => {
var id = this.getFilename(name) var id = this.getFilename(name)
var url = "/src/views/" + name + this.queryString var url = "/src/views/" + name + this.queryString
@ -147,6 +173,10 @@ class Loader{
return 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 => { this.addPromise(this.ajax("/api/songs").then(songs => {
songs = JSON.parse(songs) songs = JSON.parse(songs)
songs.forEach(song => { songs.forEach(song => {
@ -179,16 +209,22 @@ class Loader{
.filter(cat => cat.songSkin && cat.songSkin.bg_img) .filter(cat => cat.songSkin && cat.songSkin.bg_img)
.forEach(cat => { .forEach(cat => {
let name = cat.songSkin.bg_img let name = cat.songSkin.bg_img
var id = this.getFilename(name)
var image = document.createElement("img")
var url = gameConfig.assets_baseurl + "img/" + name var url = gameConfig.assets_baseurl + "img/" + name
categoryPromises.push(pageEvents.load(image).catch(response => { 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) return this.errorMsg(response, url)
})) }))
image.id = name
image.src = url
this.assetsDiv.appendChild(image)
assets.image[id] = image
}) })
this.addPromise(Promise.all(categoryPromises)) this.addPromise(Promise.all(categoryPromises))
@ -356,6 +392,7 @@ class Loader{
this.canvasTest.clean() this.canvasTest.clean()
this.clean() this.clean()
this.callback(songId) this.callback(songId)
this.ready = true
pageEvents.send("ready", readyEvent) pageEvents.send("ready", readyEvent)
}, () => this.errorMsg()) }, () => this.errorMsg())
}, () => this.errorMsg()) }, () => this.errorMsg())
@ -407,7 +444,7 @@ class Loader{
if(!lang){ if(!lang){
lang = "en" lang = "en"
} }
loader.screen.getElementsByClassName("view-content")[0].innerText = allStrings[lang].errorOccured loader.screen.getElementsByClassName("view-content")[0].innerText = allStrings[lang] && allStrings[lang].errorOccured || allStrings.en.errorOccured
} }
var loaderError = loader.screen.getElementsByClassName("loader-error-div")[0] var loaderError = loader.screen.getElementsByClassName("loader-error-div")[0]
loaderError.style.display = "flex" loaderError.style.display = "flex"
@ -472,6 +509,19 @@ class Loader{
this.screen.innerHTML = assets.pages[name] this.screen.innerHTML = assets.pages[name]
this.screen.classList[patternBg ? "add" : "remove"]("pattern-bg") 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){ ajax(url, customRequest, customResponse){
var request = new XMLHttpRequest() var request = new XMLHttpRequest()
request.open("GET", url) request.open("GET", url)

View File

@ -103,8 +103,8 @@ class LoadSong{
} }
let img = document.createElement("img") let img = document.createElement("img")
let force = imgLoad[i].type === "song" && this.touchEnabled let force = imgLoad[i].type === "song" && this.touchEnabled
if(!songObj.custom && (this.imgScale !== 1 || force)){ if(!songObj.custom){
img.crossOrigin = "Anonymous" img.crossOrigin = "anonymous"
} }
let promise = pageEvents.load(img) let promise = pageEvents.load(img)
this.addPromise(promise.then(() => { this.addPromise(promise.then(() => {
@ -147,15 +147,30 @@ class LoadSong{
} }
if(this.touchEnabled && !assets.image["touch_drum"]){ if(this.touchEnabled && !assets.image["touch_drum"]){
let img = document.createElement("img") let img = document.createElement("img")
if(this.imgScale !== 1){ img.crossOrigin = "anonymous"
img.crossOrigin = "Anonymous"
}
var url = gameConfig.assets_baseurl + "img/touch_drum.png" var url = gameConfig.assets_baseurl + "img/touch_drum.png"
this.addPromise(pageEvents.load(img).then(() => { this.addPromise(pageEvents.load(img).then(() => {
return this.scaleImg(img, "touch_drum", "") return this.scaleImg(img, "touch_drum", "")
}), url) }), url)
img.src = url img.src = url
} }
var resultsImg = [
"results_flowers",
"results_mikoshi",
"results_tetsuohana",
"results_tetsuohana2"
]
resultsImg.forEach(id => {
if(!assets.image[id]){
var img = document.createElement("img")
img.crossOrigin = "anonymous"
var url = gameConfig.assets_baseurl + "img/" + id + ".png"
this.addPromise(pageEvents.load(img).then(() => {
return this.scaleImg(img, id, "")
}), url)
img.src = url
}
})
if(songObj.volume && songObj.volume !== 1){ if(songObj.volume && songObj.volume !== 1){
this.promises.push(new Promise(resolve => setTimeout(resolve, 500))) this.promises.push(new Promise(resolve => setTimeout(resolve, 500)))
} }
@ -217,9 +232,7 @@ class LoadSong{
if(!(filenameAb in assets.image)){ if(!(filenameAb in assets.image)){
let img = document.createElement("img") let img = document.createElement("img")
let force = filenameAb.startsWith("bg_song_") && this.touchEnabled let force = filenameAb.startsWith("bg_song_") && this.touchEnabled
if(this.imgScale !== 1 || force){ img.crossOrigin = "anonymous"
img.crossOrigin = "Anonymous"
}
var url = gameConfig.assets_baseurl + "img/" + filenameAb + ".png" var url = gameConfig.assets_baseurl + "img/" + filenameAb + ".png"
this.addPromise(pageEvents.load(img).then(() => { this.addPromise(pageEvents.load(img).then(() => {
return this.scaleImg(img, filenameAb, "", force) return this.scaleImg(img, filenameAb, "", force)
@ -235,32 +248,29 @@ class LoadSong{
if(force && scale > 0.5){ if(force && scale > 0.5){
scale = 0.5 scale = 0.5
} }
if(scale !== 1){ var canvas = document.createElement("canvas")
var canvas = document.createElement("canvas") var w = Math.floor(img.width * scale)
var w = Math.floor(img.width * scale) var h = Math.floor(img.height * scale)
var h = Math.floor(img.height * scale) canvas.width = Math.max(1, w)
canvas.width = Math.max(1, w) canvas.height = Math.max(1, h)
canvas.height = Math.max(1, h) var ctx = canvas.getContext("2d")
var ctx = canvas.getContext("2d") ctx.drawImage(img, 0, 0, w, h)
ctx.drawImage(img, 0, 0, w, h) var saveScaled = url => {
var saveScaled = url => { let img2 = document.createElement("img")
let img2 = document.createElement("img") pageEvents.load(img2).then(() => {
pageEvents.load(img2).then(() => { assets.image[prefix + filename] = img2
assets.image[prefix + filename] = img2 loader.assetsDiv.appendChild(img2)
resolve() resolve()
}, reject) }, reject)
img2.src = url img2.id = prefix + filename
} img2.src = url
if("toBlob" in canvas){ }
canvas.toBlob(blob => { if("toBlob" in canvas){
saveScaled(URL.createObjectURL(blob)) canvas.toBlob(blob => {
}) saveScaled(URL.createObjectURL(blob))
}else{ })
saveScaled(canvas.toDataURL())
}
}else{ }else{
assets.image[prefix + filename] = img saveScaled(canvas.toDataURL())
resolve()
} }
}) })
} }

View File

@ -582,6 +582,12 @@ class EditFunction extends EditValue{
if(this.name){ if(this.name){
this.original = this.name[0][this.name[1]] this.original = this.name[0][this.name[1]]
} }
if(typeof this.original !== "function"){
console.error(this.loadCallback)
var error = new Error()
error.stack = "Error editing the function value of " + this.getName() + ": Original value is not a function"
throw error
}
var args = plugins.argsFromFunc(this.original) var args = plugins.argsFromFunc(this.original)
try{ try{
var output = this.loadCallback(plugins.strFromFunc(this.original), args) var output = this.loadCallback(plugins.strFromFunc(this.original), args)
@ -618,8 +624,13 @@ class EditFunction extends EditValue{
} }
class Patch{ class Patch{
edits = [] constructor(...args){
addedLanguages = [] this.init(...args)
}
init(){
this.edits = []
this.addedLanguages = []
}
addEdits(...args){ addEdits(...args){
args.forEach(arg => this.edits.push(arg)) args.forEach(arg => this.edits.push(arg))
} }

637
public/src/js/search.js Normal file
View File

@ -0,0 +1,637 @@
class Search{
constructor(...args){
this.init(...args)
}
init(songSelect){
this.songSelect = songSelect
this.opened = false
this.enabled = true
this.style = document.createElement("style")
var css = []
for(var i in this.songSelect.songSkin){
var skin = this.songSelect.songSkin[i]
if("id" in skin || i === "default"){
var id = "id" in skin ? ("cat" + skin.id) : i
css.push(loader.cssRuleset({
[".song-search-" + id]: {
"background-color": skin.background
},
[".song-search-" + id + "::before"]: {
"border-color": skin.border[0],
"border-bottom-color": skin.border[1],
"border-right-color": skin.border[1]
},
[".song-search-" + id + " .song-search-result-title::before, .song-search-" + id + " .song-search-result-subtitle::before"]: {
"-webkit-text-stroke-color": skin.outline
}
}))
}
}
this.style.appendChild(document.createTextNode(css.join("\n")))
loader.screen.appendChild(this.style)
}
perform(query){
var results = []
var filters = {}
var querySplit = query.split(" ")
var editedSplit = query.split(" ")
querySplit.forEach(word => {
if(word.length > 0){
var parts = word.toLowerCase().split(":")
if(parts.length > 1){
switch(parts[0]){
case "easy":
case "normal":
case "hard":
case "oni":
case "ura":
var range = this.parseRange(parts[1])
if(range){
filters[parts[0]] = range
}
break
case "extreme":
var range = this.parseRange(parts[1])
if(range){
filters.oni = this.parseRange(parts[1])
}
break
case "clear":
case "silver":
case "gold":
case "genre":
case "lyrics":
case "creative":
case "played":
case "maker":
case "diverge":
case "random":
filters[parts[0]] = parts[1]
break
}
editedSplit.splice(editedSplit.indexOf(word), 1)
}
}
})
query = editedSplit.join(" ").trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "")
var totalFilters = Object.keys(filters).length
var random = false
for(var i = 0; i < assets.songs.length; i++){
var song = assets.songs[i]
var passedFilters = 0
Object.keys(filters).forEach(filter => {
var value = filters[filter]
switch(filter){
case "easy":
case "normal":
case "hard":
case "oni":
case "ura":
if(song.courses[filter] && song.courses[filter].stars >= value.min && song.courses[filter].stars <= value.max){
passedFilters++
}
break
case "clear":
case "silver":
case "gold":
if(value === "any"){
var score = scoreStorage.scores[song.hash]
scoreStorage.difficulty.forEach(difficulty => {
if(score && score[difficulty] && score[difficulty].crown && (filter === "clear" || score[difficulty].crown === filter)){
passedFilters++
}
})
} else {
var score = scoreStorage.scores[song.hash]
if(score && score[value] && score[value].crown && (filter === "clear" || score[value].crown === filter)){
passedFilters++
}
}
break
case "played":
var score = scoreStorage.scores[song.hash]
if((value === "yes" && score) || (value === "no" && !score)){
passedFilters++
}
break
case "lyrics":
if((value === "yes" && song.lyrics) || (value === "no" && !song.lyrics)){
passedFilters++
}
break
case "creative":
if((value === "yes" && song.maker) || (value === "no" && !song.maker)){
passedFilters++
}
break
case "maker":
if(song.maker && song.maker.name.toLowerCase().includes(value.toLowerCase())){
passedFilters++
}
break
case "genre":
var cat = assets.categories.find(cat => cat.id === song.category_id)
var aliases = cat.aliases ? cat.aliases.concat([cat.title]) : [cat.title]
if(aliases.find(alias => alias.toLowerCase() === value.toLowerCase())){
passedFilters++
}
break
case "diverge":
var branch = Object.values(song.courses).find(course => course && course.branch)
if((value === "yes" && branch) || (value === "no" && !branch)){
passedFilters++
}
break
case "random":
if(value === "yes" || value === "no"){
random = value === "yes"
passedFilters++
}
break
}
})
if(passedFilters === totalFilters){
results.push(song)
}
}
var maxResults = totalFilters > 0 && !query ? 100 : 50
if(query){
results = fuzzysort.go(query, results, {
keys: ["titlePrepared", "subtitlePrepared"],
allowTypo: true,
limit: maxResults,
scoreFn: a => {
if(a[0]){
var score0 = a[0].score
a[0].ranges = this.indexesToRanges(a[0].indexes)
if(a[0].indexes.length > 1){
var rangeAmount = a[0].ranges.length
var lastIdx = -3
a[0].ranges.forEach(range => {
if(range[0] - lastIdx <= 2){
rangeAmount--
score0 -= 1000
}
lastIdx = range[1]
})
var index = a[0].target.toLowerCase().indexOf(query)
if(index !== -1){
a[0].ranges = [[index, index + query.length - 1]]
}else if(rangeAmount > a[0].indexes.length / 2){
score0 = -Infinity
a[0].ranges = null
}else if(rangeAmount !== 1){
score0 -= 9000
}
}
}
if(a[1]){
var score1 = a[1].score - 1000
a[1].ranges = this.indexesToRanges(a[1].indexes)
if(a[1].indexes.length > 1){
var rangeAmount = a[1].ranges.length
var lastIdx = -3
a[1].ranges.forEach(range => {
if(range[0] - lastIdx <= 2){
rangeAmount--
score1 -= 1000
}
lastIdx = range[1]
})
var index = a[1].target.indexOf(query)
if(index !== -1){
a[1].ranges = [[index, index + query.length - 1]]
}else if(rangeAmount > a[1].indexes.length / 2){
score1 = -Infinity
a[1].ranges = null
}else if(rangeAmount !== 1){
score1 -= 9000
}
}
}
if(a[0]){
return a[1] ? Math.max(score0, score1) : score0
}else{
return a[1] ? score1 : -Infinity
}
}
})
}else{
results = results.slice(0, maxResults).map(result => {
return {obj: result}
})
}
if(random){
for(var i = results.length - 1; i > 0; i--){
var j = Math.floor(Math.random() * (i + 1))
var temp = results[i]
results[i] = results[j]
results[j] = temp
}
}
return results
}
createResult(result, resultWidth, fontSize){
var song = result.obj
var title = this.songSelect.getLocalTitle(song.title, song.title_lang)
var subtitle = this.songSelect.getLocalTitle(title === song.title ? song.subtitle : "", song.subtitle_lang)
var id = "default"
if(song.category_id){
var cat = assets.categories.find(cat => cat.id === song.category_id)
if(cat && "id" in cat){
id = "cat" + cat.id
}
}
var resultDiv = document.createElement("div")
resultDiv.classList.add("song-search-result", "song-search-" + id)
resultDiv.dataset.songId = song.id
var resultInfoDiv = document.createElement("div")
resultInfoDiv.classList.add("song-search-result-info")
var resultInfoTitle = document.createElement("span")
resultInfoTitle.classList.add("song-search-result-title")
resultInfoTitle.appendChild(this.highlightResult(title, result[0]))
resultInfoTitle.setAttribute("alt", title)
resultInfoDiv.appendChild(resultInfoTitle)
if(subtitle){
resultInfoDiv.appendChild(document.createElement("br"))
var resultInfoSubtitle = document.createElement("span")
resultInfoSubtitle.classList.add("song-search-result-subtitle")
resultInfoSubtitle.appendChild(this.highlightResult(subtitle, result[1]))
resultInfoSubtitle.setAttribute("alt", subtitle)
resultInfoDiv.appendChild(resultInfoSubtitle)
}
resultDiv.appendChild(resultInfoDiv)
var courses = ["easy", "normal", "hard", "oni", "ura"]
courses.forEach(course => {
var courseDiv = document.createElement("div")
courseDiv.classList.add("song-search-result-course", "song-search-result-" + course)
if (song.courses[course]) {
var crown = "noclear"
if (scoreStorage.scores[song.hash]) {
if (scoreStorage.scores[song.hash][course]) {
crown = scoreStorage.scores[song.hash][course].crown || "noclear"
}
}
var courseCrown = document.createElement("div")
courseCrown.classList.add("song-search-result-crown", "song-search-result-" + crown)
var courseStars = document.createElement("div")
courseStars.classList.add("song-search-result-stars")
courseStars.innerText = song.courses[course].stars + "\u2605"
courseDiv.appendChild(courseCrown)
courseDiv.appendChild(courseStars)
} else {
courseDiv.classList.add("song-search-result-hidden")
}
resultDiv.appendChild(courseDiv)
})
this.songSelect.ctx.font = (1.2 * fontSize) + "px " + strings.font
var titleWidth = this.songSelect.ctx.measureText(title).width
var titleRatio = resultWidth / titleWidth
if(titleRatio < 1){
resultInfoTitle.style.transform = "scale(" + titleRatio + ", 1)"
}
if(subtitle){
this.songSelect.ctx.font = (0.8 * 1.2 * fontSize) + "px " + strings.font
var subtitleWidth = this.songSelect.ctx.measureText(subtitle).width
var subtitleRatio = resultWidth / subtitleWidth
if(subtitleRatio < 1){
resultInfoSubtitle.style.transform = "scale(" + subtitleRatio + ", 1)"
}
}
return resultDiv
}
highlightResult(text, result){
var fragment = document.createDocumentFragment()
var ranges = (result ? result.ranges : null) || []
var lastIdx = 0
ranges.forEach(range => {
if(lastIdx !== range[0]){
fragment.appendChild(document.createTextNode(text.slice(lastIdx, range[0])))
}
var span = document.createElement("span")
span.classList.add("highlighted-text")
span.innerText = text.slice(range[0], range[1] + 1)
fragment.appendChild(span)
lastIdx = range[1] + 1
})
if(text.length !== lastIdx){
fragment.appendChild(document.createTextNode(text.slice(lastIdx)))
}
return fragment
}
setActive(idx){
this.songSelect.playSound("se_ka")
var active = this.div.querySelector(":scope .song-search-result-active")
if(active){
active.classList.remove("song-search-result-active")
}
if(idx === null){
this.active = null
return
}
var el = this.results[idx]
this.input.blur()
el.classList.add("song-search-result-active")
this.scrollTo(el)
this.active = idx
}
display(fromButton=false){
if(!this.enabled){
return
}
if(this.opened){
return this.remove(true)
}
this.opened = true
this.results = []
this.div = document.createElement("div")
this.div.innerHTML = assets.pages["search"]
this.container = this.div.querySelector(":scope #song-search-container")
if(this.touchEnabled){
this.container.classList.add("touch-enabled")
}
pageEvents.add(this.container, ["mousedown", "touchstart"], this.onClick.bind(this))
this.input = this.div.querySelector(":scope #song-search-input")
this.input.setAttribute("placeholder", strings.search.searchInput)
pageEvents.add(this.input, ["input"], this.onInput.bind(this))
this.songSelect.playSound("se_pause")
loader.screen.appendChild(this.div)
this.setTip()
cancelTouch = false
noResizeRoot = true
if(this.songSelect.songs[this.songSelect.selectedSong].courses){
snd.previewGain.setVolumeMul(0.5)
}else if(this.songSelect.bgmEnabled){
snd.musicGain.setVolumeMul(0.5)
}
setTimeout(() => {
this.input.focus()
this.input.setSelectionRange(0, this.input.value.length)
}, 10)
var lastQuery = localStorage.getItem("lastSearchQuery")
if(lastQuery){
this.input.value = lastQuery
this.input.dispatchEvent(new Event("input", {
value: lastQuery
}))
}
}
remove(byUser=false){
if(this.opened){
this.opened = false
if(byUser){
this.songSelect.playSound("se_cancel")
}
pageEvents.remove(this.div.querySelector(":scope #song-search-container"),
["mousedown", "touchstart"])
pageEvents.remove(this.input, ["input"])
this.div.remove()
delete this.results
delete this.div
delete this.input
delete this.tip
delete this.active
cancelTouch = true
noResizeRoot = false
if(this.songSelect.songs[this.songSelect.selectedSong].courses){
snd.previewGain.setVolumeMul(1)
}else if(this.songSelect.bgmEnabled){
snd.musicGain.setVolumeMul(1)
}
}
}
setTip(tip, error=false){
if(this.tip){
this.tip.remove()
delete this.tip
}
if(!tip){
tip = strings.search.tip + " " + strings.search.tips[Math.floor(Math.random() * strings.search.tips.length)]
}
var resultsDiv = this.div.querySelector(":scope #song-search-results")
resultsDiv.innerHTML = ""
this.results = []
this.tip = document.createElement("div")
this.tip.id = "song-search-tip"
this.tip.innerText = tip
this.div.querySelector(":scope #song-search").appendChild(this.tip)
if(error){
this.tip.classList.add("song-search-tip-error")
}
}
proceed(songId){
var song = this.songSelect.songs.find(song => song.id === songId)
this.remove()
this.songSelect.playBgm(false)
var songIndex = this.songSelect.songs.findIndex(song => song.id === songId)
this.songSelect.setSelectedSong(songIndex)
this.songSelect.toSelectDifficulty()
}
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
}
}
}
parseRange(string){
var range = string.split("-")
if(range.length == 1){
var min = parseInt(range[0]) || 0
return min > 0 ? {min: min, max: min} : false
} else if(range.length == 2){
var min = parseInt(range[0]) || 0
var max = parseInt(range[1]) || 0
return min > 0 && max > 0 ? {min: min, max: max} : false
}
}
indexesToRanges(indexes){
var ranges = []
var range
indexes.forEach(idx => {
if(range && range[1] === idx - 1){
range[1] = idx
}else{
range = [idx, idx]
ranges.push(range)
}
})
return ranges
}
onInput(){
var text = this.input.value
localStorage.setItem("lastSearchQuery", text)
text = text.toLowerCase()
if(text.length === 0){
this.setTip()
return
}
var new_results = this.perform(text)
if(new_results.length === 0){
this.setTip(strings.search.noResults, true)
return
}else if(this.tip){
this.tip.remove()
delete this.tip
}
var resultsDiv = this.div.querySelector(":scope #song-search-results")
resultsDiv.innerHTML = ""
this.results = []
var fontSize = parseFloat(getComputedStyle(this.div.querySelector(":scope #song-search")).fontSize.slice(0, -2))
var resultsWidth = parseFloat(getComputedStyle(resultsDiv).width.slice(0, -2))
var vmin = Math.min(innerWidth, lastHeight) / 100
var courseWidth = Math.min(3 * fontSize * 1.2, 7 * vmin)
var resultWidth = resultsWidth - 1.8 * fontSize - 0.8 * fontSize - (courseWidth + 0.4 * fontSize * 1.2) * 5 - 0.6 * fontSize
this.songSelect.ctx.save()
var fragment = document.createDocumentFragment()
new_results.forEach(result => {
var result = this.createResult(result, resultWidth, fontSize)
fragment.appendChild(result)
this.results.push(result)
})
resultsDiv.appendChild(fragment)
this.songSelect.ctx.restore()
}
onClick(e){
if((e.target.id === "song-search-container" || e.target.id === "song-search-close") && e.which === 1){
this.remove(true)
}else if(e.which === 1){
var songEl = e.target.closest(".song-search-result")
if(songEl){
var songId = parseInt(songEl.dataset.songId)
this.proceed(songId)
}
}
}
keyPress(pressed, name, event, repeat){
if(name === "back" || (event && event.keyCode && event.keyCode === 70 && ctrl)) {
this.remove(true)
if(event){
event.preventDefault()
}
}else if(name === "down" && this.results.length){
if(this.input == document.activeElement && this.results){
this.setActive(0)
}else if(this.active === this.results.length - 1){
this.setActive(null)
this.input.focus()
}else if(Number.isInteger(this.active)){
this.setActive(this.active + 1)
}else{
this.setActive(0)
}
}else if(name === "up" && this.results.length){
if(this.input == document.activeElement && this.results){
this.setActive(this.results.length - 1)
}else if(this.active === 0){
this.setActive(null)
this.input.focus()
setTimeout(() => {
this.input.setSelectionRange(this.input.value.length, this.input.value.length)
}, 0)
}else if(Number.isInteger(this.active)){
this.setActive(this.active - 1)
}else{
this.setActive(this.results.length - 1)
}
}else if(name === "confirm"){
if(Number.isInteger(this.active)){
this.proceed(parseInt(this.results[this.active].dataset.songId))
}else{
this.onInput()
}
}
}
redraw(){
if(this.opened && this.container){
var vmin = Math.min(innerWidth, lastHeight) / 100
if(this.vmin !== vmin){
this.container.style.setProperty("--vmin", vmin + "px")
this.vmin = vmin
}
}else{
this.vmin = null
}
}
clean(){
loader.screen.removeChild(this.style)
fuzzysort.cleanup()
delete this.container
delete this.style
delete this.songSelect
}
}

View File

@ -217,15 +217,18 @@ class SettingsView{
constructor(...args){ constructor(...args){
this.init(...args) this.init(...args)
} }
init(touchEnabled, tutorial, songId, toSetting, settingsItems){ init(touchEnabled, tutorial, songId, toSetting, settingsItems, noSoundStart){
this.touchEnabled = touchEnabled this.touchEnabled = touchEnabled
this.tutorial = tutorial this.tutorial = tutorial
this.songId = songId this.songId = songId
this.customSettings = !!settingsItems this.customSettings = !!settingsItems
this.settingsItems = settingsItems || settings.items this.settingsItems = settingsItems || settings.items
this.locked = false
loader.changePage("settings", tutorial) loader.changePage("settings", tutorial)
assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992) if(!noSoundStart){
assets.sounds["bgm_settings"].playLoop(0.1, false, 0, 1.392, 26.992)
}
this.defaultButton = document.getElementById("settings-default") this.defaultButton = document.getElementById("settings-default")
this.viewOuter = this.getElement("view-outer") this.viewOuter = this.getElement("view-outer")
if(touchEnabled){ if(touchEnabled){
@ -377,16 +380,48 @@ class SettingsView{
this.items.push(outputObject) this.items.push(outputObject)
this.getValue(i, valueDiv) this.getValue(i, valueDiv)
} }
this.items.push({ var selectBack = this.items.length === 0
id: "default", if(this.customSettings){
settingBox: this.defaultButton var form = document.createElement("form")
}) this.browse = document.createElement("input")
this.addTouch(this.defaultButton, this.defaultSettings.bind(this)) this.browse.id = "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)
loader.screen.appendChild(form)
this.browseButton = document.createElement("div")
this.browseButton.classList.add("taibtn", "stroke-sub")
this.defaultButton.parentNode.insertBefore(this.browseButton, this.defaultButton)
this.items.push({
id: "browse",
settingBox: this.browseButton
})
this.addTouch(this.browseButton, () => {
this.playSound("se_don")
this.browse.click()
})
}
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({ this.items.push({
id: "back", id: "back",
settingBox: this.endButton settingBox: this.endButton
}) })
this.addTouch(this.endButton, this.onEnd.bind(this)) this.addTouch(this.endButton, this.onEnd.bind(this))
if(selectBack){
this.selected = this.items.length - 1
this.endButton.classList.add("selected")
}
if(!this.customSettings){ if(!this.customSettings){
this.gamepadSettings = document.getElementById("settings-gamepad") this.gamepadSettings = document.getElementById("settings-gamepad")
@ -606,6 +641,9 @@ class SettingsView{
valueDiv.innerText = value valueDiv.innerText = value
} }
setValue(name){ setValue(name){
if(this.locked){
return
}
var promise var promise
var current = this.settingsItems[name] var current = this.settingsItems[name]
if(current.getItem){ if(current.getItem){
@ -674,6 +712,9 @@ class SettingsView{
}) })
} }
keyPressed(pressed, name, event, repeat){ keyPressed(pressed, name, event, repeat){
if(this.locked){
return
}
if(pressed){ if(pressed){
if(!this.pressedKeys[name]){ if(!this.pressedKeys[name]){
this.pressedKeys[name] = this.getMS() + 300 this.pressedKeys[name] = this.getMS() + 300
@ -693,6 +734,11 @@ class SettingsView{
this.onEnd() this.onEnd()
}else if(selected.id === "default"){ }else if(selected.id === "default"){
this.defaultSettings() this.defaultSettings()
}else if(selected.id === "browse"){
if(event){
this.playSound("se_don")
this.browse.click()
}
}else{ }else{
this.setValue(selected.id) this.setValue(selected.id)
} }
@ -700,7 +746,7 @@ class SettingsView{
selected.settingBox.classList.remove("selected") selected.settingBox.classList.remove("selected")
do{ do{
this.selected = this.mod(this.items.length, this.selected + ((name === "right" || name === "down") ? 1 : -1)) this.selected = this.mod(this.items.length, this.selected + ((name === "right" || name === "down") ? 1 : -1))
}while(this.items[this.selected].id === "default" && name !== "left") }while((this.items[this.selected].id === "default" || this.items[this.selected].id === "browse") && name !== "left")
selected = this.items[this.selected] selected = this.items[this.selected]
selected.settingBox.classList.add("selected") selected.settingBox.classList.add("selected")
this.scrollTo(selected.settingBox) this.scrollTo(selected.settingBox)
@ -1027,7 +1073,9 @@ class SettingsView{
defaultSettings(){ defaultSettings(){
if(this.customSettings){ if(this.customSettings){
plugins.unloadImported() plugins.unloadImported()
return this.onEnd() this.clean(true)
this.playSound("se_don")
return setTimeout(() => this.restart(), 500)
} }
if(this.mode === "keyboard"){ if(this.mode === "keyboard"){
this.keyboardBack(this.items[this.selected]) this.keyboardBack(this.items[this.selected])
@ -1046,6 +1094,31 @@ class SettingsView{
this.drumSounds = settings.getItem("latency").drumSounds this.drumSounds = settings.getItem("latency").drumSounds
this.playSound("se_don") 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(){ onEnd(){
if(this.mode === "number"){ if(this.mode === "number"){
this.numberBack(this.items[this.selected]) this.numberBack(this.items[this.selected])
@ -1063,6 +1136,12 @@ class SettingsView{
} }
}, 500) }, 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){ getLocalTitle(title, titleLang){
if(titleLang){ if(titleLang){
for(var id in titleLang){ for(var id in titleLang){
@ -1109,14 +1188,18 @@ class SettingsView{
setStrings(){ setStrings(){
this.setAltText(this.viewTitle, this.customSettings ? strings.plugins.title : strings.gameSettings) this.setAltText(this.viewTitle, this.customSettings ? strings.plugins.title : strings.gameSettings)
this.setAltText(this.endButton, strings.settings.ok) this.setAltText(this.endButton, strings.settings.ok)
if(!this.customSettings){ if(this.customSettings){
this.setAltText(this.browseButton, strings.plugins.browse)
}else{
this.setAltText(this.gamepadTitle, strings.settings.gamepadLayout.name) this.setAltText(this.gamepadTitle, strings.settings.gamepadLayout.name)
this.setAltText(this.gamepadEndButton, strings.settings.ok) this.setAltText(this.gamepadEndButton, strings.settings.ok)
this.setAltText(this.latencyTitle, strings.settings.latency.name) this.setAltText(this.latencyTitle, strings.settings.latency.name)
this.setAltText(this.latencyDefaultButton, strings.settings.default) this.setAltText(this.latencyDefaultButton, strings.settings.default)
this.setAltText(this.latencyEndButton, strings.settings.ok) this.setAltText(this.latencyEndButton, strings.settings.ok)
} }
this.setAltText(this.defaultButton, this.customSettings ? strings.plugins.unloadAll : strings.settings.default) if(this.showDefault){
this.setAltText(this.defaultButton, this.customSettings ? strings.plugins.unloadAll : strings.settings.default)
}
} }
setAltText(element, text){ setAltText(element, text){
element.innerText = text element.innerText = text
@ -1154,11 +1237,13 @@ class SettingsView{
getMS(){ getMS(){
return Date.now() return Date.now()
} }
clean(){ clean(noSoundStop){
this.redrawRunning = false this.redrawRunning = false
this.keyboard.clean() this.keyboard.clean()
this.gamepad.clean() this.gamepad.clean()
assets.sounds["bgm_settings"].stop() if(!noSoundStop){
assets.sounds["bgm_settings"].stop()
}
pageEvents.remove(window, ["mouseup", "touchstart", "touchmove", "touchend", "blur"], this.windowSymbol) pageEvents.remove(window, ["mouseup", "touchstart", "touchmove", "touchend", "blur"], this.windowSymbol)
if(this.customSettings){ if(this.customSettings){
pageEvents.remove(window, "language-change", this.windowSymbol) pageEvents.remove(window, "language-change", this.windowSymbol)
@ -1176,7 +1261,12 @@ class SettingsView{
if(this.defaultButton){ if(this.defaultButton){
delete this.defaultButton delete this.defaultButton
} }
if(!this.customSettings){ if(this.customSettings){
pageEvents.remove(this.browse, "change")
this.removeTouch(this.browseButton)
delete this.browse
delete this.browseButton
}else{
this.removeTouch(this.gamepadSettings) this.removeTouch(this.gamepadSettings)
this.removeTouch(this.gamepadEndButton) this.removeTouch(this.gamepadEndButton)
this.removeTouch(this.gamepadBox) this.removeTouch(this.gamepadBox)
@ -1204,8 +1294,12 @@ class SettingsView{
delete this.latencyEndButton delete this.latencyEndButton
if(this.resolution !== settings.getItem("resolution")){ if(this.resolution !== settings.getItem("resolution")){
for(var i in assets.image){ for(var i in assets.image){
if(i === "touch_drum" || i.startsWith("bg_song_") || i.startsWith("bg_stage_") || i.startsWith("bg_don_")){ if(i === "touch_drum" || i.startsWith("bg_song_") || i.startsWith("bg_stage_") || i.startsWith("bg_don_") || i.startsWith("results_")){
URL.revokeObjectURL(assets.image[i].src) var img = assets.image[i]
URL.revokeObjectURL(img.src)
if(img.parentNode){
img.parentNode.removeChild(img)
}
delete assets.image[i] delete assets.image[i]
} }
} }

View File

@ -92,22 +92,6 @@ class SongSelect{
} }
this.songSkin["default"].sort = songSkinLength + 1 this.songSkin["default"].sort = songSkinLength + 1
this.searchStyle = document.createElement("style")
var searchCss = []
Object.keys(this.songSkin).forEach(key => {
var skin = this.songSkin[key]
if("id" in skin || key === "default"){
var id = "id" in skin ? ("cat" + skin.id) : key
searchCss.push('.song-search-' + id + ' { background-color: ' + skin.background + ' }')
searchCss.push('.song-search-' + id + '::before { border: 0.4em solid ' + skin.border[0] + ' ; border-bottom-color: ' + skin.border[1] + ' ; border-right-color: ' + skin.border[1] + ' }')
searchCss.push('.song-search-' + id + ' .song-search-result-title::before { -webkit-text-stroke: 0.4em ' + skin.outline + ' }')
searchCss.push('.song-search-' + id + ' .song-search-result-subtitle::before { -webkit-text-stroke: 0.4em ' + skin.outline + ' }')
}
})
this.searchStyle.appendChild(document.createTextNode(searchCss.join("\n")))
loader.screen.appendChild(this.searchStyle)
this.font = strings.font this.font = strings.font
this.songs = [] this.songs = []
@ -194,14 +178,12 @@ class SongSelect{
category: strings.random category: strings.random
}) })
} }
if(plugins.hasSettings()){ this.songs.push({
this.songs.push({ title: strings.plugins.title,
title: strings.plugins.title, skin: this.songSkin.plugins,
skin: this.songSkin.plugins, action: "plugins",
action: "plugins", category: strings.random
category: strings.random })
})
}
this.songs.push({ this.songs.push({
title: strings.back, title: strings.back,
@ -246,6 +228,8 @@ class SongSelect{
this.currentSongCache = new CanvasCache(noSmoothing) this.currentSongCache = new CanvasCache(noSmoothing)
this.nameplateCache = new CanvasCache(noSmoothing) this.nameplateCache = new CanvasCache(noSmoothing)
this.search = new Search(this)
this.difficulty = [strings.easy, strings.normal, strings.hard, strings.oni] this.difficulty = [strings.easy, strings.normal, strings.hard, strings.oni]
this.difficultyId = ["easy", "normal", "hard", "oni", "ura"] this.difficultyId = ["easy", "normal", "hard", "oni", "ura"]
@ -257,7 +241,6 @@ class SongSelect{
this.selectedSong = 0 this.selectedSong = 0
this.selectedDiff = 0 this.selectedDiff = 0
this.lastCurrentSong = {} this.lastCurrentSong = {}
this.searchEnabled = true
this.lastRandom = false this.lastRandom = false
assets.sounds["bgm_songsel"].playLoop(0.1, false, 0, 1.442, 3.506) assets.sounds["bgm_songsel"].playLoop(0.1, false, 0, 1.442, 3.506)
@ -434,44 +417,14 @@ class SongSelect{
this.state.showWarning = false this.state.showWarning = false
this.showWarning = false this.showWarning = false
} }
}else if (this.search){ }else if(this.search.opened){
if(name === "back" || (event && event.keyCode && event.keyCode === 70 && ctrl)) { this.search.keyPress(pressed, name, event, repeat)
this.removeSearch(true)
if(event){ event.preventDefault() }
}else if(name === "down" && this.search.results.length){
if(this.search.input == document.activeElement && this.search.results){
this.searchSetActive(0)
}else if(this.search.active === this.search.results.length-1){
this.searchSetActive(null)
this.search.input.focus()
}else if(Number.isInteger(this.search.active)){
this.searchSetActive(this.search.active+1)
}else{
this.searchSetActive(0)
}
}else if(name === "up" && this.search.results.length){
if(this.search.input == document.activeElement && this.search.results){
this.searchSetActive(this.search.results.length-1)
}else if(this.search.active === 0){
this.searchSetActive(null)
this.search.input.focus()
setTimeout(() => {
this.search.input.setSelectionRange(this.search.input.value.length, this.search.input.value.length)
}, 0)
}else if(Number.isInteger(this.search.active)){
this.searchSetActive(this.search.active-1)
}else{
this.searchSetActive(this.search.results.length-1)
}
}else if(name === "confirm"){
if(Number.isInteger(this.search.active)){
this.searchProceed(parseInt(this.search.results[this.search.active].dataset.songId))
}
}
}else if(this.state.screen === "song"){ }else if(this.state.screen === "song"){
if(event && event.keyCode && event.keyCode === 70 && ctrl){ if(event && event.keyCode && event.keyCode === 70 && ctrl){
this.displaySearch() this.search.display()
if(event){ event.preventDefault() } if(event){
event.preventDefault()
}
}else if(name === "confirm"){ }else if(name === "confirm"){
this.toSelectDifficulty() this.toSelectDifficulty()
}else if(name === "back"){ }else if(name === "back"){
@ -504,8 +457,10 @@ class SongSelect{
} }
}else if(this.state.screen === "difficulty"){ }else if(this.state.screen === "difficulty"){
if(event && event.keyCode && event.keyCode === 70 && ctrl){ if(event && event.keyCode && event.keyCode === 70 && ctrl){
this.displaySearch() this.search.display()
if(event){ event.preventDefault() } if(event){
event.preventDefault()
}
}else if(name === "confirm"){ }else if(name === "confirm"){
if(this.selectedDiff === 0){ if(this.selectedDiff === 0){
this.toSongSelect() this.toSongSelect()
@ -528,8 +483,10 @@ class SongSelect{
} }
}else if(this.state.screen === "title" || this.state.screen === "titleFadeIn"){ }else if(this.state.screen === "title" || this.state.screen === "titleFadeIn"){
if(event && event.keyCode && event.keyCode === 70 && ctrl){ if(event && event.keyCode && event.keyCode === 70 && ctrl){
this.displaySearch() this.search.display()
if(event){ event.preventDefault() } if(event){
event.preventDefault()
}
} }
} }
} }
@ -627,7 +584,7 @@ class SongSelect{
if(408 < mouse.x && mouse.x < 872 && 470 < mouse.y && mouse.y < 550){ if(408 < mouse.x && mouse.x < 872 && 470 < mouse.y && mouse.y < 550){
moveTo = "showWarning" moveTo = "showWarning"
} }
}else if(this.state.screen === "song" && !this.search){ }else if(this.state.screen === "song" && !this.search.opened){
if(20 < mouse.y && mouse.y < 90 && 410 < mouse.x && mouse.x < 880 && (mouse.x < 540 || mouse.x > 750)){ if(20 < mouse.y && mouse.y < 90 && 410 < mouse.x && mouse.x < 880 && (mouse.x < 540 || mouse.x > 750)){
moveTo = mouse.x < 640 ? "categoryPrev" : "categoryNext" moveTo = mouse.x < 640 ? "categoryPrev" : "categoryNext"
}else if(!p2.session && 60 < mouse.x && mouse.x < 332 && 640 < mouse.y && mouse.y < 706 && gameConfig.accounts){ }else if(!p2.session && 60 < mouse.x && mouse.x < 332 && 640 < mouse.y && mouse.y < 706 && gameConfig.accounts){
@ -792,7 +749,7 @@ class SongSelect{
} }
} }
}else if(this.state.locked === 0 || fromP2){ }else if(this.state.locked === 0 || fromP2){
this.removeSearch() this.search.remove()
if(currentSong.courses){ if(currentSong.courses){
if(currentSong.unloaded){ if(currentSong.unloaded){
return return
@ -815,7 +772,6 @@ class SongSelect{
} }
pageEvents.send("song-select-difficulty", currentSong) pageEvents.send("song-select-difficulty", currentSong)
}else if(currentSong.action === "back"){ }else if(currentSong.action === "back"){
this.clean()
this.toTitleScreen() this.toTitleScreen()
}else if(currentSong.action === "random"){ }else if(currentSong.action === "random"){
do{ do{
@ -827,7 +783,7 @@ class SongSelect{
this.toSelectDifficulty(false, playVoice=false) this.toSelectDifficulty(false, playVoice=false)
pageEvents.send("song-select-random") pageEvents.send("song-select-random")
}else if(currentSong.action === "search"){ }else if(currentSong.action === "search"){
this.displaySearch(true) this.search.display(true)
}else if(currentSong.action === "tutorial"){ }else if(currentSong.action === "tutorial"){
this.toTutorial() this.toTutorial()
}else if(currentSong.action === "about"){ }else if(currentSong.action === "about"){
@ -1116,8 +1072,8 @@ class SongSelect{
this.selectableText = "" this.selectableText = ""
if(this.search && this.searchContainer){ if(this.search.opened && this.search.container){
this.searchInput() this.search.onInput()
} }
}else if(!document.hasFocus() && !p2.session){ }else if(!document.hasFocus() && !p2.session){
if(this.state.focused){ if(this.state.focused){
@ -1146,15 +1102,7 @@ class SongSelect{
var screen = this.state.screen var screen = this.state.screen
var selectedWidth = this.songAsset.width var selectedWidth = this.songAsset.width
if(this.search && this.searchContainer){ this.search.redraw()
var vmin = Math.min(innerWidth, lastHeight) / 100
if(this.vmin !== vmin){
this.searchContainer.style.setProperty("--vmin", vmin + "px")
this.vmin = vmin
}
}else{
this.vmin = null
}
if(this.wheelScrolls !== 0 && !this.state.locked && ms >= this.wheelTimer + 20) { if(this.wheelScrolls !== 0 && !this.state.locked && ms >= this.wheelTimer + 20) {
if(p2.session){ if(p2.session){
@ -2726,520 +2674,6 @@ class SongSelect{
} }
return addedSong return addedSong
} }
createSearchResult(result, resultWidth, fontSize){
var song = result.obj
var title = this.getLocalTitle(song.title, song.title_lang)
var subtitle = this.getLocalTitle(title === song.title ? song.subtitle : "", song.subtitle_lang)
var id = "default"
if(song.category_id){
var cat = assets.categories.find(cat => cat.id === song.category_id)
if(cat && "id" in cat){
id = "cat" + cat.id
}
}
var resultDiv = document.createElement("div")
resultDiv.classList.add("song-search-result", "song-search-" + id)
resultDiv.dataset.songId = song.id
var resultInfoDiv = document.createElement("div")
resultInfoDiv.classList.add("song-search-result-info")
var resultInfoTitle = document.createElement("span")
resultInfoTitle.classList.add("song-search-result-title")
resultInfoTitle.appendChild(this.highlightResult(title, result[0]))
resultInfoTitle.setAttribute("alt", title)
resultInfoDiv.appendChild(resultInfoTitle)
if(subtitle){
resultInfoDiv.appendChild(document.createElement("br"))
var resultInfoSubtitle = document.createElement("span")
resultInfoSubtitle.classList.add("song-search-result-subtitle")
resultInfoSubtitle.appendChild(this.highlightResult(subtitle, result[1]))
resultInfoSubtitle.setAttribute("alt", subtitle)
resultInfoDiv.appendChild(resultInfoSubtitle)
}
resultDiv.appendChild(resultInfoDiv)
var courses = ["easy", "normal", "hard", "oni", "ura"]
courses.forEach(course => {
var courseDiv = document.createElement("div")
courseDiv.classList.add("song-search-result-course", "song-search-result-" + course)
if (song.courses[course]) {
var crown = "noclear"
if (scoreStorage.scores[song.hash]) {
if (scoreStorage.scores[song.hash][course]) {
crown = scoreStorage.scores[song.hash][course].crown || "noclear"
}
}
var courseCrown = document.createElement("div")
courseCrown.classList.add("song-search-result-crown", "song-search-result-" + crown)
var courseStars = document.createElement("div")
courseStars.classList.add("song-search-result-stars")
courseStars.innerText = song.courses[course].stars + '★'
courseDiv.appendChild(courseCrown)
courseDiv.appendChild(courseStars)
} else {
courseDiv.classList.add("song-search-result-hidden")
}
resultDiv.appendChild(courseDiv)
})
this.ctx.font = (1.2 * fontSize) + "px " + strings.font
var titleWidth = this.ctx.measureText(title).width
var titleRatio = resultWidth / titleWidth
if(titleRatio < 1){
resultInfoTitle.style.transform = "scale(" + titleRatio + ", 1)"
}
if(subtitle){
this.ctx.font = (0.8 * 1.2 * fontSize) + "px " + strings.font
var subtitleWidth = this.ctx.measureText(subtitle).width
var subtitleRatio = resultWidth / subtitleWidth
if(subtitleRatio < 1){
resultInfoSubtitle.style.transform = "scale(" + subtitleRatio + ", 1)"
}
}
return resultDiv
}
highlightResult(text, result){
var fragment = document.createDocumentFragment()
var ranges = (result ? result.ranges : null) || []
var lastIdx = 0
ranges.forEach(range => {
if(lastIdx !== range[0]){
fragment.appendChild(document.createTextNode(text.slice(lastIdx, range[0])))
}
var span = document.createElement("span")
span.classList.add("highlighted-text")
span.innerText = text.slice(range[0], range[1] + 1)
fragment.appendChild(span)
lastIdx = range[1] + 1
})
if(text.length !== lastIdx){
fragment.appendChild(document.createTextNode(text.slice(lastIdx)))
}
return fragment
}
searchSetActive(idx){
this.playSound("se_ka")
var active = this.search.div.querySelector(":scope .song-search-result-active")
if(active){
active.classList.remove("song-search-result-active")
}
if(idx === null){
this.search.active = null
return
}
var el = this.search.results[idx]
this.search.input.blur()
el.classList.add("song-search-result-active")
this.scrollTo(el)
this.search.active = idx
}
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
}
}
}
displaySearch(fromButton=false){
if(!this.searchEnabled){
return
}
if(this.search){
return this.removeSearch(true)
}
this.search = {results: []}
this.search.div = document.createElement("div")
this.search.div.innerHTML = assets.pages["search"]
this.searchContainer = this.search.div.querySelector(":scope #song-search-container")
if(this.touchEnabled){
this.searchContainer.classList.add("touch-enabled")
}
pageEvents.add(this.searchContainer, ["mousedown", "touchstart"], this.searchClick.bind(this))
this.search.input = this.search.div.querySelector(":scope #song-search-input")
this.search.input.setAttribute("placeholder", strings.search.searchInput)
pageEvents.add(this.search.input, ["input"], this.searchInput.bind(this))
this.playSound("se_pause")
loader.screen.appendChild(this.search.div)
this.setSearchTip()
cancelTouch = false
noResizeRoot = true
if(this.songs[this.selectedSong].courses){
snd.previewGain.setVolumeMul(0.5)
}else if(this.bgmEnabled){
snd.musicGain.setVolumeMul(0.5)
}
setTimeout(() => {
this.search.input.focus()
this.search.input.setSelectionRange(0, this.search.input.value.length)
}, 10)
var lastQuery = localStorage.getItem("lastSearchQuery")
if(lastQuery){
this.search.input.value = lastQuery
this.search.input.dispatchEvent(new Event('input', {value: lastQuery}))
}
}
removeSearch(byUser=false){
if(this.search){
if(byUser){
this.playSound("se_cancel")
}
pageEvents.remove(this.search.div.querySelector(":scope #song-search-container"),
["mousedown", "touchstart"])
pageEvents.remove(this.search.input, ["input"])
this.search.div.remove()
delete this.search
cancelTouch = true
noResizeRoot = false
if(this.songs[this.selectedSong].courses){
snd.previewGain.setVolumeMul(1)
}else if(this.bgmEnabled){
snd.musicGain.setVolumeMul(1)
}
}
}
setSearchTip(tip, error=false){
if(this.search.tip){
this.search.tip.remove()
delete this.search.tip
}
if(!tip){
tip = strings.search.tip + " " + strings.search.tips[Math.floor(Math.random() * strings.search.tips.length)]
}
var resultsDiv = this.search.div.querySelector(":scope #song-search-results")
resultsDiv.innerHTML = ""
this.search.results = []
this.search.tip = document.createElement("div")
this.search.tip.setAttribute("id", "song-search-tip")
this.search.tip.innerText = tip
this.search.div.querySelector(":scope #song-search").appendChild(this.search.tip)
if(error){
this.search.tip.classList.add("song-search-tip-error")
}
}
parseRange(string){
var range = string.split("-")
if(range.length == 1){
var min = parseInt(range[0]) || 0
return min > 0 ? {min: min, max: min} : false
} else if(range.length == 2){
var min = parseInt(range[0]) || 0
var max = parseInt(range[1]) || 0
return min > 0 && max > 0 ? {min: min, max: max} : false
}
}
performSearch(query){
var results = []
var filters = {}
var querySplit = query.split(" ")
var editedSplit = query.split(" ")
querySplit.forEach(word => {
if(word.length > 0){
var parts = word.toLowerCase().split(":")
if(parts.length > 1){
switch(parts[0]){
case "easy":
case "normal":
case "hard":
case "oni":
case "ura":
var range = this.parseRange(parts[1])
if (range) { filters[parts[0]] = range }
break
case "extreme":
var range = this.parseRange(parts[1])
if (range) { filters.oni = this.parseRange(parts[1]) }
break
case "clear":
case "silver":
case "gold":
case "genre":
case "lyrics":
case "creative":
case "played":
case "maker":
case "diverge":
filters[parts[0]] = parts[1]
break
}
editedSplit.splice(editedSplit.indexOf(word), 1)
}
}
})
query = editedSplit.join(" ").trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "")
var totalFilters = Object.keys(filters).length
for(var i = 0; i < assets.songs.length; i++){
var song = assets.songs[i]
var passedFilters = 0
Object.keys(filters).forEach(filter => {
var value = filters[filter]
switch(filter){
case "easy":
case "normal":
case "hard":
case "oni":
case "ura":
if(song.courses[filter] && song.courses[filter].stars >= value.min && song.courses[filter].stars <= value.max){
passedFilters++
}
break
case "clear":
case "silver":
case "gold":
if(value === "any"){
var score = scoreStorage.scores[song.hash]
scoreStorage.difficulty.forEach(difficulty => {
if(score && score[difficulty] && score[difficulty].crown && (filter === "clear" || score[difficulty].crown === filter)){
passedFilters++
}
})
} else {
var score = scoreStorage.scores[song.hash]
if(score && score[value] && score[value].crown && (filter === "clear" || score[value].crown === filter)){
passedFilters++
}
}
break
case "played":
var score = scoreStorage.scores[song.hash]
if((value === "yes" && score) || (value === "no" && !score)){
passedFilters++
}
break
case "lyrics":
if((value === "yes" && song.lyrics) || (value === "no" && !song.lyrics)){
passedFilters++
}
break
case "creative":
if((value === "yes" && song.maker) || (value === "no" && !song.maker)){
passedFilters++
}
break
case "maker":
if(song.maker && song.maker.name.toLowerCase().includes(value.toLowerCase())){
passedFilters++
}
break
case "genre":
var cat = assets.categories.find(cat => cat.id === song.category_id)
var aliases = cat.aliases ? cat.aliases.concat([cat.title]) : [cat.title]
if(aliases.find(alias => alias.toLowerCase() === value.toLowerCase())){
passedFilters++
}
break
case "diverge":
var branch = Object.values(song.courses).find(course => course && course.branch)
if((value === "yes" && branch) || (value === "no" && !branch)){
passedFilters++
}
break
}
})
if(passedFilters === totalFilters){
results.push(song)
}
}
var maxResults = totalFilters > 0 && !query ? 100 : 50
if(query){
results = fuzzysort.go(query, results, {
keys: ["titlePrepared", "subtitlePrepared"],
allowTypo: true,
limit: maxResults,
scoreFn: a => {
if(a[0]){
var score0 = a[0].score
a[0].ranges = this.indexesToRanges(a[0].indexes)
if(a[0].indexes.length > 1){
var rangeAmount = a[0].ranges.length
var lastIdx = -3
a[0].ranges.forEach(range => {
if(range[0] - lastIdx <= 2){
rangeAmount--
score0 -= 1000
}
lastIdx = range[1]
})
var index = a[0].target.toLowerCase().indexOf(query)
if(index !== -1){
a[0].ranges = [[index, index + query.length - 1]]
}else if(rangeAmount > a[0].indexes.length / 2){
score0 = -Infinity
a[0].ranges = null
}else if(rangeAmount !== 1){
score0 -= 9000
}
}
}
if(a[1]){
var score1 = a[1].score - 1000
a[1].ranges = this.indexesToRanges(a[1].indexes)
if(a[1].indexes.length > 1){
var rangeAmount = a[1].ranges.length
var lastIdx = -3
a[1].ranges.forEach(range => {
if(range[0] - lastIdx <= 2){
rangeAmount--
score1 -= 1000
}
lastIdx = range[1]
})
var index = a[1].target.indexOf(query)
if(index !== -1){
a[1].ranges = [[index, index + query.length - 1]]
}else if(rangeAmount > a[1].indexes.length / 2){
score1 = -Infinity
a[1].ranges = null
}else if(rangeAmount !== 1){
score1 -= 9000
}
}
}
if(a[0]){
return a[1] ? Math.max(score0, score1) : score0
}else{
return a[1] ? score1 : -Infinity
}
}
})
}else{
results = results.map(result => {
return {obj: result}
}).slice(0, maxResults)
}
return results
}
indexesToRanges(indexes){
var ranges = []
var range
indexes.forEach(idx => {
if(range && range[1] === idx - 1){
range[1] = idx
}else{
range = [idx, idx]
ranges.push(range)
}
})
return ranges
}
searchInput(){
var text = this.search.input.value
localStorage.setItem("lastSearchQuery", text)
text = text.toLowerCase()
if(text.length === 0){
this.setSearchTip()
return
}
var new_results = this.performSearch(text)
if (new_results.length === 0) {
this.setSearchTip(strings.search.noResults, true)
return
} else if (this.search.tip) {
this.search.tip.remove()
delete this.search.tip
}
var resultsDiv = this.search.div.querySelector(":scope #song-search-results")
resultsDiv.innerHTML = ""
this.search.results = []
var fontSize = parseFloat(getComputedStyle(this.search.div.querySelector(":scope #song-search")).fontSize.slice(0, -2))
var resultsWidth = parseFloat(getComputedStyle(resultsDiv).width.slice(0, -2))
var vmin = Math.min(innerWidth, lastHeight) / 100
var courseWidth = Math.min(3 * fontSize * 1.2, 7 * vmin)
var resultWidth = resultsWidth - 1.8 * fontSize - 0.8 * fontSize - (courseWidth + 0.4 * fontSize * 1.2) * 5 - 0.6 * fontSize
this.ctx.save()
var fragment = document.createDocumentFragment()
new_results.forEach(result => {
var result = this.createSearchResult(result, resultWidth, fontSize)
fragment.appendChild(result)
this.search.results.push(result)
})
resultsDiv.appendChild(fragment)
this.ctx.restore()
}
searchClick(e){
if((e.target.id === "song-search-container" || e.target.id === "song-search-close") && e.which === 1){
this.removeSearch(true)
}else if(e.which === 1){
var songEl = e.target.closest(".song-search-result")
if(songEl){
var songId = parseInt(songEl.dataset.songId)
this.searchProceed(songId)
}
}
}
searchProceed(songId){
var song = this.songs.find(song => song.id === songId)
this.removeSearch()
this.playBgm(false)
var songIndex = this.songs.findIndex(song => song.id === songId)
this.setSelectedSong(songIndex)
this.toSelectDifficulty()
}
onusers(response){ onusers(response){
var p2InSong = false var p2InSong = false
@ -3268,17 +2702,17 @@ class SongSelect{
if(this.state.screen !== "difficulty"){ if(this.state.screen !== "difficulty"){
this.toSelectDifficulty({player: response.value.player}) this.toSelectDifficulty({player: response.value.player})
} }
this.searchEnabled = false this.search.enabled = false
p2InSong = true p2InSong = true
this.removeSearch() this.search.remove()
} }
} }
} }
}) })
} }
if(!this.searchEnabled && !p2InSong){ if(!this.search.enabled && !p2InSong){
this.searchEnabled = true this.search.enabled = true
} }
} }
onsongsel(response){ onsongsel(response){
@ -3404,6 +2838,7 @@ class SongSelect{
this.sessionCache.clean() this.sessionCache.clean()
this.currentSongCache.clean() this.currentSongCache.clean()
this.nameplateCache.clean() this.nameplateCache.clean()
this.search.clean()
assets.sounds["bgm_songsel"].stop() assets.sounds["bgm_songsel"].stop()
if(!this.bgmEnabled){ if(!this.bgmEnabled){
snd.musicGain.fadeIn() snd.musicGain.fadeIn()
@ -3425,13 +2860,8 @@ class SongSelect{
pageEvents.remove(this.touchFullBtn, "click") pageEvents.remove(this.touchFullBtn, "click")
delete this.touchFullBtn delete this.touchFullBtn
} }
if(this.searchStyle){
loader.screen.removeChild(this.searchStyle)
}
delete this.selectable delete this.selectable
delete this.ctx delete this.ctx
delete this.canvas delete this.canvas
delete this.searchContainer
delete this.searchStyle
} }
} }

View File

@ -1331,6 +1331,17 @@ var translations = {
version: { version: {
ja: "Ver. %s", ja: "Ver. %s",
en: "Version %s" en: "Version %s"
},
browse: {
ja: "参照する…",
en: "Browse...",
cn: "浏览…",
tw: "開啟檔案…",
ko: "찾아보기…"
},
noPlugins: {
ja: null,
en: "No .taikoweb.js plugin files have been found in the provided file list."
} }
}, },
search: { search: {

View File

@ -8,6 +8,7 @@ class Titlescreen{
if(!songId){ if(!songId){
loader.changePage("titlescreen", false) loader.changePage("titlescreen", false)
loader.screen.style.backgroundImage = ""
this.titleScreen = document.getElementById("title-screen") this.titleScreen = document.getElementById("title-screen")
this.proceed = document.getElementById("title-proceed") this.proceed = document.getElementById("title-proceed")
@ -75,8 +76,9 @@ class Titlescreen{
} }
pageEvents.remove(p2, "message") pageEvents.remove(p2, "message")
if(this.customFolder && !fromP2 && !assets.customSongs){ if(this.customFolder && !fromP2 && !assets.customSongs){
var customSongs = new CustomSongs(this.touched, true) var customSongs = new CustomSongs(this.touched, true, true)
var soundPlayed = false var soundPlayed = false
var noError = true
var promises = [] var promises = []
var allFiles = [] var allFiles = []
this.customFolder.forEach(file => { this.customFolder.forEach(file => {
@ -95,6 +97,13 @@ class Titlescreen{
setTimeout(() => { setTimeout(() => {
new SongSelect(false, false, this.touched, this.songId) new SongSelect(false, false, this.touched, this.songId)
}, 500) }, 500)
noError = false
}).then(() => {
if(noError){
setTimeout(() => {
new SongSelect("customSongs", false, this.touchEnabled)
}, 500)
}
}) })
}else{ }else{
setTimeout(() => { setTimeout(() => {

View File

@ -12,8 +12,9 @@
if(noSmoothing){ if(noSmoothing){
this.ctx.imageSmoothingEnabled = false this.ctx.imageSmoothingEnabled = false
} }
if(resolution === "lowest"){ this.multiplayer = this.controller.multiplayer
this.canvas.style.imageRendering = "pixelated" if(this.multiplayer !== 2 && resolution === "lowest"){
document.getElementById("game").classList.add("pixelated")
} }
this.gameDiv = document.getElementById("game") this.gameDiv = document.getElementById("game")
@ -97,7 +98,6 @@
this.branchCache = new CanvasCache(noSmoothing) this.branchCache = new CanvasCache(noSmoothing)
this.nameplateCache = new CanvasCache(noSmoothing) this.nameplateCache = new CanvasCache(noSmoothing)
this.multiplayer = this.controller.multiplayer
if(this.multiplayer === 2){ if(this.multiplayer === 2){
this.player = p2.player === 2 ? 1 : 2 this.player = p2.player === 2 ? 1 : 2
}else{ }else{

View File

@ -7,6 +7,8 @@
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="description" content="パソコンとスマホのブラウザ向けの太鼓の達人シミュレータ 🥁 Taiko no Tatsujin rhythm game simulator for desktop and mobile browsers"> <meta name="description" content="パソコンとスマホのブラウザ向けの太鼓の達人シミュレータ 🥁 Taiko no Tatsujin rhythm game simulator for desktop and mobile browsers">
<meta name="keywords" content="taiko no tatsujin, taiko, don chan, online, rhythm, browser, html5, game, for browsers, pc, arcade, emulator, free, download, website, 太鼓の達人, 太鼓ウェブ, 太鼓之達人, 太鼓達人, 太鼓网页, 网页版, 太鼓網頁, 網頁版, 태고의 달인, 태고 웹"> <meta name="keywords" content="taiko no tatsujin, taiko, don chan, online, rhythm, browser, html5, game, for browsers, pc, arcade, emulator, free, download, website, 太鼓の達人, 太鼓ウェブ, 太鼓之達人, 太鼓達人, 太鼓网页, 网页版, 太鼓網頁, 網頁版, 태고의 달인, 태고 웹">
<meta name="robots" content="notranslate">
<meta name="robots" content="noimageindex">
<meta name="color-scheme" content="only light"> <meta name="color-scheme" content="only light">
<link rel="stylesheet" href="/src/css/loader.css?{{version.commit_short}}"> <link rel="stylesheet" href="/src/css/loader.css?{{version.commit_short}}">
@ -22,7 +24,7 @@
<div id="screen" class="pattern-bg"></div> <div id="screen" class="pattern-bg"></div>
<div data-nosnippet id="version"> <div data-nosnippet id="version">
{% if version.version and version.commit_short and version.commit %} {% if version.version and version.commit_short and version.commit %}
<a href="{{version.url}}commit/{{version.commit}}" target="_blank" id="version-link" class="stroke-sub" alt="taiko-web ver.{{version.version}} ({{version.commit_short}})">taiko-web ver.{{version.version}} ({{version.commit_short}})</a> <a href="{{version.url}}commit/{{version.commit}}" target="_blank" rel="noopener" id="version-link" class="stroke-sub" alt="taiko-web ver.{{version.version}} ({{version.commit_short}})">taiko-web ver.{{version.version}} ({{version.commit_short}})</a>
{% else %} {% else %}
<a href="{{version.url}}" target="_blank" rel="noopener" id="version-link" class="stroke-sub" alt="taiko-web (unknown version)">taiko-web (unknown version)</a> <a href="{{version.url}}" target="_blank" rel="noopener" id="version-link" class="stroke-sub" alt="taiko-web (unknown version)">taiko-web (unknown version)</a>
{% endif %} {% endif %}