Merge branch 'master' into normalize-search

This commit is contained in:
Bui 2022-03-22 00:36:33 +00:00
commit 7d3cff5cbe
22 changed files with 1130 additions and 821 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{
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;
z-index: 1;
box-sizing: border-box;
background-size: auto, 3.12em;
background-position: 0%, -2%;
}
#song-search-container.touch-enabled{
@ -96,6 +98,7 @@
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
border: 0.4em solid;
}
.song-search-result:last-of-type {
@ -133,6 +136,7 @@
content: attr(alt);
position: absolute;
z-index: -1;
-webkit-text-stroke-width: 0.4em;
}
.song-search-result-course {

View File

@ -452,3 +452,21 @@ kbd{
#dropzone.dragover{
opacity: 1;
}
.plugin-browse-button{
position: relative;
overflow: hidden;
}
#plugin-browse{
position: absolute;
font-size: inherit;
top: -0.1em;
left: -0.1em;
right: -0.1em;
bottom: -0.1em;
border-radius: 0.5em;
opacity: 0;
cursor: pointer;
}
#plugin-browse::-webkit-file-upload-button{
cursor: pointer;
}

View File

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

View File

@ -38,7 +38,8 @@ var assets = {
"customsongs.js",
"abstractfile.js",
"idb.js",
"plugins.js"
"plugins.js",
"search.js"
],
"css": [
"main.css",
@ -50,20 +51,13 @@ var assets = {
"view.css",
"search.css"
],
"assetsCss": [
"img/img.css"
],
"img": [
"title-screen.png",
"notes.png",
"notes_drumroll.png",
"notes_hit.png",
"notes_explosion.png",
"balloon.png",
"taiko.png",
"dancing-don.gif",
"bg-pattern-1.png",
"difficulty.png",
"don_anim_normal_a.png",
"don_anim_normal_b1.png",
"don_anim_normal_b2.png",
@ -81,24 +75,26 @@ var assets = {
"don_anim_clear_b2.png",
"fire_anim.png",
"fireworks_anim.png",
"bg_genre_def.png",
"bg_score_p1.png",
"bg_score_p2.png",
"bg_settings.png",
"bg_pause.png",
"badge_auto.png",
"touch_pause.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"
"mimizu.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": [
"se_pause.ogg",
"se_calibration.ogg",

View File

@ -2,7 +2,7 @@ class CustomSongs{
constructor(...args){
this.init(...args)
}
init(touchEnabled, noPage){
init(touchEnabled, noPage, noLoading){
this.loaderDiv = document.createElement("div")
this.loaderDiv.innerHTML = assets.pages["loadsong"]
var loadingText = this.loaderDiv.querySelector("#loading-text")
@ -13,6 +13,7 @@ class CustomSongs{
if(noPage){
this.noPage = true
this.noLoading = noLoading
return
}
@ -262,11 +263,13 @@ class CustomSongs{
var importSongs = new ImportSongs()
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.loading(false)
if(e === "nosongs"){
this.showError(strings.customSongs.noSongs)
this.showError(strings.customSongs.noSongs, "nosongs")
}else if(e !== "cancel"){
return Promise.reject(e)
}
@ -308,13 +311,15 @@ class CustomSongs{
this.locked = false
this.loading(false)
if(e === "nosongs"){
this.showError(strings.customSongs.noSongs)
this.showError(strings.customSongs.noSongs, "nosongs")
}else if(e !== "cancel"){
return Promise.reject(e)
}
}).finally(() => {
var addRemove = !gpicker || !gpicker.oauthToken ? "add" : "remove"
this.linkGdriveAccount.classList[addRemove]("hiddenbtn")
if(this.linkGdriveAccount){
var addRemove = !gpicker || !gpicker.oauthToken ? "add" : "remove"
this.linkGdriveAccount.classList[addRemove]("hiddenbtn")
}
})
}
gdriveAccount(event){
@ -369,7 +374,7 @@ class CustomSongs{
open("privacy")
}
loading(show){
if(this.noPage){
if(this.noLoading){
return
}
if(show){
@ -385,14 +390,16 @@ class CustomSongs{
assets.customSongs = true
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()
setTimeout(() => {
new SongSelect("customSongs", false, this.touchEnabled)
pageEvents.send("import-songs", length)
}, 500)
}
this.clean()
setTimeout(() => {
new SongSelect("customSongs", false, this.touchEnabled)
pageEvents.send("import-songs", length)
}, 500)
return songs && songs.length
}
keyPressed(pressed, name){
@ -472,10 +479,14 @@ class CustomSongs{
resolve()
}, 500))
}
showError(text){
showError(text, errorName){
this.locked = 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
}
this.mode = "error"
@ -514,6 +525,9 @@ class CustomSongs{
pageEvents.remove(document, ["dragover", "dragleave", "drop"])
delete this.dropzone
}
if(gpicker){
gpicker.tokenResolve = null
}
delete this.browse
delete this.linkLocalFolder
delete this.linkGdriveFolder

View File

@ -11,6 +11,7 @@ class Gpicker{
this.filesUrl = "https://www.googleapis.com/drive/v3/files/"
this.resolveQueue = []
this.queueActive = false
this.clientCallbackBind = this.clientCallback.bind(this)
}
browse(lockedCallback, errorCallback){
return this.loadApi()
@ -123,9 +124,12 @@ class Gpicker{
if(window.gapi && gapi.client && gapi.client.drive){
return Promise.resolve()
}
return loader.loadScript("https://apis.google.com/js/api.js")
.then(() => new Promise((resolve, reject) =>
gapi.load("auth2:picker:client", {
var promises = [
loader.loadScript("https://apis.google.com/js/api.js"),
loader.loadScript("https://accounts.google.com/gsi/client")
]
return Promise.all(promises).then(() => new Promise((resolve, reject) =>
gapi.load("picker:client", {
callback: resolve,
onerror: reject
})
@ -134,58 +138,53 @@ class Gpicker{
gapi.client.load("drive", "v3").then(resolve, reject)
))
}
getAuth(errorCallback=()=>{}){
if(!this.auth){
return new Promise((resolve, reject) => {
gapi.auth2.init({
clientId: this.oauthClientId,
fetch_basic_profile: false,
scope: this.scope
}).then(() => {
this.auth = gapi.auth2.getAuthInstance()
resolve(this.auth)
}, e => {
if(e.details){
var errorStr = strings.gpicker.authError.replace("%s", e.details)
if(/cookie/i.test(e.details)){
errorStr += "\n\n" + strings.gpicker.cookieError
}
errorCallback(errorStr)
}
reject(e)
})
})
getClient(errorCallback=()=>{}, force){
var obj = {
client_id: this.oauthClientId,
scope: this.scope,
callback: this.clientCallbackBind
}
if(force){
if(!this.clientForce){
obj.select_account = true
this.clientForce = google.accounts.oauth2.initTokenClient(obj)
}
return this.clientForce
}else{
return Promise.resolve(this.auth)
if(!this.client){
this.client = google.accounts.oauth2.initTokenClient(obj)
}
return this.client
}
}
clientCallback(tokenResponse){
this.tokenResponse = tokenResponse
this.oauthToken = tokenResponse && tokenResponse.access_token
if(this.oauthToken && this.tokenResolve){
this.tokenResolve()
}
}
getToken(lockedCallback=()=>{}, errorCallback=()=>{}, force){
if(this.oauthToken && !force){
return Promise.resolve()
}
return this.getAuth(errorCallback).then(auth => {
var user = force || auth.currentUser.get()
if(force || !this.checkScope(user)){
lockedCallback(false)
return auth.signIn(force ? {
prompt: "select_account"
} : undefined).then(user => {
if(this.checkScope(user)){
lockedCallback(true)
}else{
return Promise.reject("cancel")
}
}, () => Promise.reject("cancel"))
var client = this.getClient(errorCallback, force)
var promise = new Promise(resolve => {
this.tokenResolve = resolve
})
lockedCallback(false)
client.requestAccessToken()
return promise.then(() => {
this.tokenResolve = null
if(this.checkScope()){
lockedCallback(true)
}else{
return Promise.reject("cancel")
}
})
}
checkScope(user){
if(user.hasGrantedScopes(this.scope)){
this.oauthToken = user.getAuthResponse(true).access_token
return this.oauthToken
}else{
return false
}
checkScope(){
return google.accounts.oauth2.hasGrantedAnyScope(this.tokenResponse, this.scope)
}
switchAccounts(lockedCallback, errorCallback){
return this.loadApi().then(() => this.getToken(lockedCallback, errorCallback, true))
@ -221,12 +220,12 @@ class Gpicker{
.build()
.setVisible(true)
}
downloadFile(id, arrayBuffer, retry){
downloadFile(id, responseType, retry){
var url = this.filesUrl + id + "?alt=media"
return this.queue().then(this.getToken.bind(this)).then(() =>
loader.ajax(url, request => {
if(arrayBuffer){
request.responseType = "arraybuffer"
if(responseType){
request.responseType = responseType
}
request.setRequestHeader("Authorization", "Bearer " + this.oauthToken)
}, true).then(event => {
@ -239,7 +238,7 @@ class Gpicker{
var e = response.error
if(e && e.errors[0].reason === "authError"){
delete this.oauthToken
return this.downloadFile(id, arrayBuffer, true)
return this.downloadFile(id, responseType, true)
}else{
return reject()
}

View File

@ -61,12 +61,6 @@ class Loader{
stylesheet.href = "/src/css/" + name + this.queryString
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 = () => {
if(document.styleSheets.length >= cssCount){
resolve()
@ -84,9 +78,10 @@ class Loader{
}), url)
}
assets.img.forEach(name=>{
assets.img.forEach(name => {
var id = this.getFilename(name)
var image = document.createElement("img")
image.crossOrigin = "anonymous"
var url = gameConfig.assets_baseurl + "img/" + name
this.addPromise(pageEvents.load(image), url)
image.id = name
@ -95,6 +90,37 @@ class Loader{
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 => {
var id = this.getFilename(name)
var url = "/src/views/" + name + this.queryString
@ -147,6 +173,10 @@ class Loader{
return
}
var style = document.createElement("style")
style.appendChild(document.createTextNode(css.join("\n")))
document.head.appendChild(style)
this.addPromise(this.ajax("/api/songs").then(songs => {
songs = JSON.parse(songs)
songs.forEach(song => {
@ -179,16 +209,22 @@ class Loader{
.filter(cat => cat.songSkin && cat.songSkin.bg_img)
.forEach(cat => {
let name = cat.songSkin.bg_img
var id = this.getFilename(name)
var image = document.createElement("img")
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)
}))
image.id = name
image.src = url
this.assetsDiv.appendChild(image)
assets.image[id] = image
})
this.addPromise(Promise.all(categoryPromises))
@ -356,6 +392,7 @@ class Loader{
this.canvasTest.clean()
this.clean()
this.callback(songId)
this.ready = true
pageEvents.send("ready", readyEvent)
}, () => this.errorMsg())
}, () => this.errorMsg())
@ -407,7 +444,7 @@ class Loader{
if(!lang){
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]
loaderError.style.display = "flex"
@ -472,6 +509,19 @@ class Loader{
this.screen.innerHTML = assets.pages[name]
this.screen.classList[patternBg ? "add" : "remove"]("pattern-bg")
}
cssRuleset(rulesets){
var css = []
for(var selector in rulesets){
var declarationsObj = rulesets[selector]
var declarations = []
for(var property in declarationsObj){
var value = declarationsObj[property]
declarations.push("\t" + property + ": " + value + ";")
}
css.push(selector + "{\n" + declarations.join("\n") + "\n}")
}
return css.join("\n")
}
ajax(url, customRequest, customResponse){
var request = new XMLHttpRequest()
request.open("GET", url)

View File

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

View File

@ -104,7 +104,7 @@ var kanaPairs = [["っきゃ","ッキャ"],["っきゅ","ッキュ"],["っきょ
["ば","バ"],["び","ビ"],["ぶ","ブ"],["べ","ベ"],["ぼ","ボ"],["ぱ","パ"],["ぴ","パ"],["ぷ","プ"],["ぺ","ペ"],["ぽ","ポ"],["ゔ","ヴ"]]
pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => {
if(event.cancelable && cancelTouch && event.target.tagName !== "SELECT"){
if(event.cancelable && cancelTouch && event.target.tagName !== "SELECT" && (event.target.tagName !== "INPUT" || event.target.type !== "file")){
event.preventDefault()
}
})

View File

@ -67,13 +67,15 @@
if((name === "start" || name === "start p1") && !inSong){
inSong = true
if(!hasSong){
if(!hasSong || name === "start" && courses[courseName] && courses[courseName].startName !== "start"){
hasSong = false
if(!(courseName in courses)){
courses[courseName] = {}
}
for(var name in currentCourse){
if(name !== "branch"){
courses[courseName][name] = currentCourse[name]
courses[courseName].startName = name
for(var opt in currentCourse){
if(opt !== "branch"){
courses[courseName][opt] = currentCourse[opt]
}
}
courses[courseName].start = lineNum + 1

View File

@ -582,6 +582,12 @@ class EditFunction extends EditValue{
if(this.name){
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)
try{
var output = this.loadCallback(plugins.strFromFunc(this.original), args)
@ -618,8 +624,13 @@ class EditFunction extends EditValue{
}
class Patch{
edits = []
addedLanguages = []
constructor(...args){
this.init(...args)
}
init(){
this.edits = []
this.addedLanguages = []
}
addEdits(...args){
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){
this.init(...args)
}
init(touchEnabled, tutorial, songId, toSetting, settingsItems){
init(touchEnabled, tutorial, songId, toSetting, settingsItems, noSoundStart){
this.touchEnabled = touchEnabled
this.tutorial = tutorial
this.songId = songId
this.customSettings = !!settingsItems
this.settingsItems = settingsItems || settings.items
this.locked = false
loader.changePage("settings", tutorial)
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.viewOuter = this.getElement("view-outer")
if(touchEnabled){
@ -377,16 +380,46 @@ class SettingsView{
this.items.push(outputObject)
this.getValue(i, valueDiv)
}
this.items.push({
id: "default",
settingBox: this.defaultButton
})
this.addTouch(this.defaultButton, this.defaultSettings.bind(this))
var selectBack = this.items.length === 0
if(this.customSettings){
var form = document.createElement("form")
this.browse = document.createElement("input")
this.browse.id = "plugin-browse"
this.browse.type = "file"
this.browse.multiple = true
this.browse.accept = ".taikoweb.js"
pageEvents.add(this.browse, "change", this.browseChange.bind(this))
form.appendChild(this.browse)
this.browseButton = document.createElement("div")
this.browseButton.classList.add("taibtn", "stroke-sub", "plugin-browse-button")
this.browseText = document.createTextNode("")
this.browseButton.appendChild(this.browseText)
this.browseButton.appendChild(form)
this.defaultButton.parentNode.insertBefore(this.browseButton, this.defaultButton)
this.items.push({
id: "browse",
settingBox: this.browseButton
})
}
this.showDefault = !this.customSettings || plugins.allPlugins.filter(obj => obj.plugin.imported).length
if(this.showDefault){
this.items.push({
id: "default",
settingBox: this.defaultButton
})
this.addTouch(this.defaultButton, this.defaultSettings.bind(this))
}else{
this.defaultButton.parentNode.removeChild(this.defaultButton)
}
this.items.push({
id: "back",
settingBox: this.endButton
})
this.addTouch(this.endButton, this.onEnd.bind(this))
if(selectBack){
this.selected = this.items.length - 1
this.endButton.classList.add("selected")
}
if(!this.customSettings){
this.gamepadSettings = document.getElementById("settings-gamepad")
@ -606,6 +639,9 @@ class SettingsView{
valueDiv.innerText = value
}
setValue(name){
if(this.locked){
return
}
var promise
var current = this.settingsItems[name]
if(current.getItem){
@ -674,6 +710,9 @@ class SettingsView{
})
}
keyPressed(pressed, name, event, repeat){
if(this.locked){
return
}
if(pressed){
if(!this.pressedKeys[name]){
this.pressedKeys[name] = this.getMS() + 300
@ -693,6 +732,11 @@ class SettingsView{
this.onEnd()
}else if(selected.id === "default"){
this.defaultSettings()
}else if(selected.id === "browse"){
if(event){
this.playSound("se_don")
this.browse.click()
}
}else{
this.setValue(selected.id)
}
@ -700,7 +744,7 @@ class SettingsView{
selected.settingBox.classList.remove("selected")
do{
this.selected = this.mod(this.items.length, this.selected + ((name === "right" || name === "down") ? 1 : -1))
}while(this.items[this.selected].id === "default" && name !== "left")
}while((this.items[this.selected].id === "default" || this.items[this.selected].id === "browse") && name !== "left")
selected = this.items[this.selected]
selected.settingBox.classList.add("selected")
this.scrollTo(selected.settingBox)
@ -1027,7 +1071,9 @@ class SettingsView{
defaultSettings(){
if(this.customSettings){
plugins.unloadImported()
return this.onEnd()
this.clean(true)
this.playSound("se_don")
return setTimeout(() => this.restart(), 500)
}
if(this.mode === "keyboard"){
this.keyboardBack(this.items[this.selected])
@ -1046,6 +1092,31 @@ class SettingsView{
this.drumSounds = settings.getItem("latency").drumSounds
this.playSound("se_don")
}
browseChange(event){
this.locked = true
var files = []
for(var i = 0; i < event.target.files.length; i++){
files.push(new LocalFile(event.target.files[i]))
}
var customSongs = new CustomSongs(this.touchEnabled, true)
customSongs.importLocal(files).then(() => {
this.clean(true)
return this.restart()
}).catch(e => {
if(e){
var message = e.message
if(e.name === "nosongs"){
message = strings.plugins.noPlugins
}
if(message){
alert(message)
}
}
this.locked = false
this.browse.form.reset()
return Promise.resolve()
})
}
onEnd(){
if(this.mode === "number"){
this.numberBack(this.items[this.selected])
@ -1063,6 +1134,12 @@ class SettingsView{
}
}, 500)
}
restart(){
if(this.mode === "number"){
this.numberBack(this.items[this.selected])
}
return new SettingsView(this.touchEnabled, this.tutorial, this.songId, undefined, this.customSettings ? plugins.getSettings() : undefined, true)
}
getLocalTitle(title, titleLang){
if(titleLang){
for(var id in titleLang){
@ -1109,14 +1186,19 @@ class SettingsView{
setStrings(){
this.setAltText(this.viewTitle, this.customSettings ? strings.plugins.title : strings.gameSettings)
this.setAltText(this.endButton, strings.settings.ok)
if(!this.customSettings){
if(this.customSettings){
this.browseText.data = strings.plugins.browse
this.browseButton.setAttribute("alt", strings.plugins.browse)
}else{
this.setAltText(this.gamepadTitle, strings.settings.gamepadLayout.name)
this.setAltText(this.gamepadEndButton, strings.settings.ok)
this.setAltText(this.latencyTitle, strings.settings.latency.name)
this.setAltText(this.latencyDefaultButton, strings.settings.default)
this.setAltText(this.latencyEndButton, strings.settings.ok)
}
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){
element.innerText = text
@ -1154,11 +1236,13 @@ class SettingsView{
getMS(){
return Date.now()
}
clean(){
clean(noSoundStop){
this.redrawRunning = false
this.keyboard.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)
if(this.customSettings){
pageEvents.remove(window, "language-change", this.windowSymbol)
@ -1176,7 +1260,12 @@ class SettingsView{
if(this.defaultButton){
delete this.defaultButton
}
if(!this.customSettings){
if(this.customSettings){
pageEvents.remove(this.browse, "change")
delete this.browse
delete this.browseButton
delete this.browseText
}else{
this.removeTouch(this.gamepadSettings)
this.removeTouch(this.gamepadEndButton)
this.removeTouch(this.gamepadBox)
@ -1204,8 +1293,12 @@ class SettingsView{
delete this.latencyEndButton
if(this.resolution !== settings.getItem("resolution")){
for(var i in assets.image){
if(i === "touch_drum" || i.startsWith("bg_song_") || i.startsWith("bg_stage_") || i.startsWith("bg_don_")){
URL.revokeObjectURL(assets.image[i].src)
if(i === "touch_drum" || i.startsWith("bg_song_") || i.startsWith("bg_stage_") || i.startsWith("bg_don_") || i.startsWith("results_")){
var img = assets.image[i]
URL.revokeObjectURL(img.src)
if(img.parentNode){
img.parentNode.removeChild(img)
}
delete assets.image[i]
}
}

View File

@ -92,22 +92,6 @@ class SongSelect{
}
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.songs = []
@ -194,14 +178,12 @@ class SongSelect{
category: strings.random
})
}
if(plugins.hasSettings()){
this.songs.push({
title: strings.plugins.title,
skin: this.songSkin.plugins,
action: "plugins",
category: strings.random
})
}
this.songs.push({
title: strings.plugins.title,
skin: this.songSkin.plugins,
action: "plugins",
category: strings.random
})
this.songs.push({
title: strings.back,
@ -246,6 +228,8 @@ class SongSelect{
this.currentSongCache = new CanvasCache(noSmoothing)
this.nameplateCache = new CanvasCache(noSmoothing)
this.search = new Search(this)
this.difficulty = [strings.easy, strings.normal, strings.hard, strings.oni]
this.difficultyId = ["easy", "normal", "hard", "oni", "ura"]
@ -257,7 +241,6 @@ class SongSelect{
this.selectedSong = 0
this.selectedDiff = 0
this.lastCurrentSong = {}
this.searchEnabled = true
this.lastRandom = false
assets.sounds["bgm_songsel"].playLoop(0.1, false, 0, 1.442, 3.506)
@ -434,44 +417,14 @@ class SongSelect{
this.state.showWarning = false
this.showWarning = false
}
}else if (this.search){
if(name === "back" || (event && event.keyCode && event.keyCode === 70 && ctrl)) {
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.search.opened){
this.search.keyPress(pressed, name, event, repeat)
}else if(this.state.screen === "song"){
if(event && event.keyCode && event.keyCode === 70 && ctrl){
this.displaySearch()
if(event){ event.preventDefault() }
this.search.display()
if(event){
event.preventDefault()
}
}else if(name === "confirm"){
this.toSelectDifficulty()
}else if(name === "back"){
@ -504,8 +457,10 @@ class SongSelect{
}
}else if(this.state.screen === "difficulty"){
if(event && event.keyCode && event.keyCode === 70 && ctrl){
this.displaySearch()
if(event){ event.preventDefault() }
this.search.display()
if(event){
event.preventDefault()
}
}else if(name === "confirm"){
if(this.selectedDiff === 0){
this.toSongSelect()
@ -528,8 +483,10 @@ class SongSelect{
}
}else if(this.state.screen === "title" || this.state.screen === "titleFadeIn"){
if(event && event.keyCode && event.keyCode === 70 && ctrl){
this.displaySearch()
if(event){ event.preventDefault() }
this.search.display()
if(event){
event.preventDefault()
}
}
}
}
@ -627,7 +584,7 @@ class SongSelect{
if(408 < mouse.x && mouse.x < 872 && 470 < mouse.y && mouse.y < 550){
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)){
moveTo = mouse.x < 640 ? "categoryPrev" : "categoryNext"
}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){
this.removeSearch()
this.search.remove()
if(currentSong.courses){
if(currentSong.unloaded){
return
@ -815,7 +772,6 @@ class SongSelect{
}
pageEvents.send("song-select-difficulty", currentSong)
}else if(currentSong.action === "back"){
this.clean()
this.toTitleScreen()
}else if(currentSong.action === "random"){
do{
@ -827,7 +783,7 @@ class SongSelect{
this.toSelectDifficulty(false, playVoice=false)
pageEvents.send("song-select-random")
}else if(currentSong.action === "search"){
this.displaySearch(true)
this.search.display(true)
}else if(currentSong.action === "tutorial"){
this.toTutorial()
}else if(currentSong.action === "about"){
@ -1116,8 +1072,8 @@ class SongSelect{
this.selectableText = ""
if(this.search && this.searchContainer){
this.searchInput()
if(this.search.opened && this.search.container){
this.search.onInput()
}
}else if(!document.hasFocus() && !p2.session){
if(this.state.focused){
@ -1146,15 +1102,7 @@ class SongSelect{
var screen = this.state.screen
var selectedWidth = this.songAsset.width
if(this.search && this.searchContainer){
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
}
this.search.redraw()
if(this.wheelScrolls !== 0 && !this.state.locked && ms >= this.wheelTimer + 20) {
if(p2.session){
@ -2726,531 +2674,6 @@ class SongSelect{
}
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
}
}
normalizeSearch(string){
string = string
.replace('', '\'').replace('“', '"').replace('”', '"')
kanaPairs.forEach(pair => {
string = string.replace(pair[1], pair[0])
})
return string.normalize("NFKD").replace(/[\u0300-\u036f]/g, "")
}
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 = this.normalizeSearch(editedSplit.join(" ").trim())
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){
var p2InSong = false
@ -3279,17 +2702,17 @@ class SongSelect{
if(this.state.screen !== "difficulty"){
this.toSelectDifficulty({player: response.value.player})
}
this.searchEnabled = false
this.search.enabled = false
p2InSong = true
this.removeSearch()
this.search.remove()
}
}
}
})
}
if(!this.searchEnabled && !p2InSong){
this.searchEnabled = true
if(!this.search.enabled && !p2InSong){
this.search.enabled = true
}
}
onsongsel(response){
@ -3415,6 +2838,7 @@ class SongSelect{
this.sessionCache.clean()
this.currentSongCache.clean()
this.nameplateCache.clean()
this.search.clean()
assets.sounds["bgm_songsel"].stop()
if(!this.bgmEnabled){
snd.musicGain.fadeIn()
@ -3436,13 +2860,8 @@ class SongSelect{
pageEvents.remove(this.touchFullBtn, "click")
delete this.touchFullBtn
}
if(this.searchStyle){
loader.screen.removeChild(this.searchStyle)
}
delete this.selectable
delete this.ctx
delete this.canvas
delete this.searchContainer
delete this.searchStyle
}
}

View File

@ -1331,6 +1331,17 @@ var translations = {
version: {
ja: "Ver. %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: {

View File

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

View File

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

View File

@ -7,6 +7,8 @@
<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="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">
<link rel="stylesheet" href="/src/css/loader.css?{{version.commit_short}}">
@ -22,7 +24,7 @@
<div id="screen" class="pattern-bg"></div>
<div data-nosnippet id="version">
{% 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 %}
<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 %}

View File

@ -23,10 +23,7 @@ You can use the Google Drive integration to let Taiko Web make your Taiko chart
Applications that integrate with a Google account must declare their intent by requesting permissions. These permissions to your account must be granted in order for Taiko Web to integrate with Google accounts. Below is a list of these permissions and why they are required. At no time will Taiko Web request or have access to your Google account password.
3.1 "Associate you with your personal info on Google" Permission
Required for Google Sign-In to provide Taiko Web with a non-identifiable user authentication token. No other information provided by this permission is used.
3.2 "See and download all your Google Drive files" Permission
3.1 "See and download all your Google Drive files" Permission
When selecting a folder with the Google Drive file picker, Taiko Web instructs your Browser to recursively download all the files of that folder directly into your computer's memory. Limitation of Google Drive's permission model requires us to request access to all your Google Drive files, however, Taiko Web will only access the selected folder and its children, and only when requested. File parsing is handled locally; none of your Google Drive files is ever sent to our servers or third parties.
{% endif %}{% if config.email %}
{% if integration %}4{% else %}3{% endif %}. Contact Info

62
tools/setup.sh Normal file
View File

@ -0,0 +1,62 @@
#!/bin/bash
set -euo pipefail
sudo apt update
sudo apt install -y git python3-pip python3-virtualenv python3-venv nginx ffmpeg redis supervisor
if [[ -r /etc/os-release ]]; then
. /etc/os-release
if [[ $ID = ubuntu ]]; then
if [[ $VERSION_CODENAME = impish ]]; then
VERSION_CODENAME=focal # MongoDB does not provide packages for Ubuntu 21.10
fi
REPO="https://repo.mongodb.org/apt/ubuntu $VERSION_CODENAME/mongodb-org/5.0 multiverse"
elif [[ $ID = debian ]]; then
if [[ $VERSION_CODENAME = bullseye ]]; then
VERSION_CODENAME=buster # MongoDB does not provide packages for Debian 11 yet
fi
REPO="https://repo.mongodb.org/apt/debian $VERSION_CODENAME/mongodb-org/5.0 main"
else
echo "Unsupported distribution $ID"
exit 1
fi
else
echo "Not running a distribution with /etc/os-release available"
exit 1
fi
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] $REPO" | sudo tee /etc/apt/sources.list.d/mongodb-org-5.0.list
sudo apt update
sudo apt install -y mongodb-org
sudo mkdir -p /srv/taiko-web
sudo chown $USER /srv/taiko-web
git clone https://github.com/bui/taiko-web.git /srv/taiko-web
cd /srv/taiko-web
tools/get_version.sh
cp tools/hooks/* .git/hooks/
cp config.example.py config.py
sudo cp tools/nginx.conf /etc/nginx/conf.d/taiko-web.conf
sudo sed -i 's/^\(\s\{0,\}\)\(include \/etc\/nginx\/sites-enabled\/\*;\)$/\1#\2/g' /etc/nginx/nginx.conf
sudo sed -i 's/}/ application\/wasm wasm;\n}/g' /etc/nginx/mime.types
sudo nginx -s reload
python3 -m venv .venv
.venv/bin/pip install --upgrade pip wheel setuptools
.venv/bin/pip install -r requirements.txt
sudo mkdir -p /var/log/taiko-web
sudo cp tools/supervisor.conf /etc/supervisor/conf.d/taiko-web.conf
sudo service supervisor restart
sudo systemctl enable mongod.service
sudo service mongod start
IP=$(dig +short txt ch whoami.cloudflare @1.0.0.1 | tr -d '"')
echo
echo "Setup complete! You should be able to access your taiko-web instance at http://$IP"
echo