Merge pull request #273 from bui/importsongs-add-gdrive

ImportSongs: Add Google Drive support
This commit is contained in:
Bui 2020-11-08 07:44:56 +00:00 committed by GitHub
commit 348322b7d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1297 additions and 433 deletions

3
app.py
View File

@ -119,7 +119,8 @@ def get_config():
'assets_baseurl': config.ASSETS_BASEURL, 'assets_baseurl': config.ASSETS_BASEURL,
'email': config.EMAIL, 'email': config.EMAIL,
'accounts': config.ACCOUNTS, 'accounts': config.ACCOUNTS,
'custom_js': config.CUSTOM_JS 'custom_js': config.CUSTOM_JS,
'google_credentials': config.GOOGLE_CREDENTIALS
} }
if not config_out.get('songs_baseurl'): if not config_out.get('songs_baseurl'):

View File

@ -33,3 +33,11 @@ SECRET_KEY = 'change-me'
# Git repository base URL. # Git repository base URL.
URL = 'https://github.com/bui/taiko-web/' URL = 'https://github.com/bui/taiko-web/'
# Google Drive API.
GOOGLE_CREDENTIALS = {
'gdrive_enabled': False,
'api_key': '',
'oauth_client_id': '',
'project_number': ''
}

View File

@ -13,22 +13,23 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 20vw; width: 20vmax;
height: 15vw; height: 15vmax;
background: rgba(0, 0, 0, 0.75); background: rgba(0, 0, 0, 0.75);
border-radius: 5px; border-radius: 5px;
border: 3px solid white; border: 3px solid white;
color: #fff; color: #fff;
z-index: 1;
} }
#loading-don{ #loading-don{
width: 10vw; width: 10vmax;
height: calc(10vw / 120 * 115); height: calc(10vmax / 120 * 115);
background-size: contain; background-size: contain;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.loading-text{ .loading-text{
position: relative; position: relative;
font-size: 1.5vw; font-size: 1.5vmax;
text-align: center; text-align: center;
z-index: 1; z-index: 1;
} }

View File

@ -108,6 +108,14 @@ kbd{
.left-buttons .taibtn{ .left-buttons .taibtn{
margin-right: 0.4em; margin-right: 0.4em;
} }
.center-buttons{
display: flex;
justify-content: center;
margin: 1.5em 0;
}
.center-buttons .taibtn{
margin: 0 0.2em;
}
.diag-txt textarea, .diag-txt textarea,
.diag-txt iframe{ .diag-txt iframe{
width: 100%; width: 100%;
@ -217,7 +225,8 @@ kbd{
z-index: 1; z-index: 1;
} }
#settings-gamepad, #settings-gamepad,
#settings-latency{ #settings-latency,
#customsongs-error{
display: none; display: none;
} }
#settings-gamepad .view{ #settings-gamepad .view{
@ -289,7 +298,8 @@ kbd{
.latency-buttons span:active{ .latency-buttons span:active{
background-color: #946013; background-color: #946013;
} }
.left-buttons .taibtn{ .left-buttons .taibtn,
.center-buttons .taibtn{
z-index: 1; z-index: 1;
} }
.accountpass-form, .accountpass-form,
@ -403,3 +413,19 @@ kbd{
font-size: 1em; font-size: 1em;
padding: 0.2em; padding: 0.2em;
} }
#customsongs-error .view,
#dropzone .view{
width: 600px;
}
#dropzone{
pointer-events: none;
opacity: 0;
transition: opacity 0.5s;
}
#dropzone .view-content{
font-size: 2em;
text-align: center;
}
#dropzone.dragover{
opacity: 1;
}

View File

@ -0,0 +1,95 @@
function readFile(file, arrayBuffer, encoding){
var reader = new FileReader()
var promise = pageEvents.load(reader).then(event => event.target.result)
reader[arrayBuffer ? "readAsArrayBuffer" : "readAsText"](file, encoding)
return promise
}
class RemoteFile{
constructor(url){
this.url = url
try{
this.path = new URL(url).pathname
}catch(e){
this.path = url
}
if(this.path.startsWith("/")){
this.path = this.path.slice(1)
}
this.name = this.path
var index = this.name.lastIndexOf("/")
if(index !== -1){
this.name = this.name.slice(index + 1)
}
}
arrayBuffer(){
return loader.ajax(this.url, request => {
request.responseType = "arraybuffer"
})
}
read(encoding){
if(encoding){
return this.blob().then(blob => readFile(blob, false, encoding))
}else{
return loader.ajax(this.url)
}
}
blob(){
return this.arrayBuffer().then(response => new Blob([response]))
}
}
class LocalFile{
constructor(file, path){
this.file = file
this.path = path || file.webkitRelativePath
this.url = this.path
this.name = file.name
}
arrayBuffer(){
return readFile(this.file, true)
}
read(encoding){
return readFile(this.file, false, encoding)
}
blob(){
return Promise.resolve(this.file)
}
}
class GdriveFile{
constructor(fileObj){
this.path = fileObj.path
this.name = fileObj.name
this.id = fileObj.id
this.url = gpicker.filesUrl + this.id + "?alt=media"
}
arrayBuffer(){
return gpicker.downloadFile(this.id, true)
}
read(encoding){
if(encoding){
return this.blob().then(blob => readFile(blob, false, encoding))
}else{
return gpicker.downloadFile(this.id)
}
}
blob(){
return this.arrayBuffer().then(response => new Blob([response]))
}
}
class CachedFile{
constructor(contents, oldFile){
this.contents = contents
this.oldFile = oldFile
this.path = oldFile.path
this.name = oldFile.name
this.url = oldFile.url
}
arrayBuffer(){
return Promise.resolve(this.contents)
}
read(encoding){
return this.arrayBuffer()
}
blob(){
return this.arrayBuffer().then(response => new Blob([response]))
}
}

View File

@ -33,7 +33,9 @@ var assets = {
"settings.js", "settings.js",
"scorestorage.js", "scorestorage.js",
"account.js", "account.js",
"lyrics.js" "lyrics.js",
"customsongs.js",
"abstractfile.js"
], ],
"css": [ "css": [
"main.css", "main.css",
@ -144,7 +146,8 @@ var assets = {
"session.html", "session.html",
"settings.html", "settings.html",
"account.html", "account.html",
"login.html" "login.html",
"customsongs.html"
], ],
"songs": [], "songs": [],

View File

@ -165,7 +165,6 @@ class AutoScore {
GetMaxCombo() { GetMaxCombo() {
var combo = 0; var combo = 0;
for (var circle of this.circles) { for (var circle of this.circles) {
//alert(this.IsCommonCircle(circle));
if (this.IsCommonCircle(circle) && (!circle.branch || circle.branch.name === "master")) { if (this.IsCommonCircle(circle) && (!circle.branch || circle.branch.name === "master")) {
combo++; combo++;
} }

View File

@ -43,7 +43,7 @@ class Controller{
var maxCombo = this.parsedSongData.circles.filter(circle => ["don", "ka", "daiDon", "daiKa"].indexOf(circle.type) > -1 && (!circle.branch || circle.branch.name == "master")).length var maxCombo = this.parsedSongData.circles.filter(circle => ["don", "ka", "daiDon", "daiKa"].indexOf(circle.type) > -1 && (!circle.branch || circle.branch.name == "master")).length
if (maxCombo >= 50) { if (maxCombo >= 50) {
var comboVoices = ["v_combo_50"].concat([...Array(Math.floor(maxCombo/100)).keys()].map(i => "v_combo_" + (i + 1)*100)) var comboVoices = ["v_combo_50"].concat(Array.from(Array(Math.min(50, Math.floor(maxCombo / 100))), (d, i) => "v_combo_" + ((i + 1) * 100)))
var promises = [] var promises = []
comboVoices.forEach(name => { comboVoices.forEach(name => {
@ -246,23 +246,15 @@ class Controller{
var songObj = assets.songs.find(song => song.id === this.selectedSong.folder) var songObj = assets.songs.find(song => song.id === this.selectedSong.folder)
var promises = [] var promises = []
if(songObj.chart && songObj.chart !== "blank"){ if(songObj.chart && songObj.chart !== "blank"){
var reader = new FileReader() promises.push(songObj.chart.read(this.selectedSong.type === "tja" ? "sjis" : undefined).then(data => {
promises.push(pageEvents.load(reader).then(event => { this.songData = data.replace(/\0/g, "").split("\n")
this.songData = event.target.result.replace(/\0/g, "").split("\n")
return Promise.resolve() return Promise.resolve()
})) }))
if(this.selectedSong.type === "tja"){
reader.readAsText(songObj.chart, "sjis")
}else{
reader.readAsText(songObj.chart)
}
} }
if(songObj.lyricsFile){ if(songObj.lyricsFile){
var reader = new FileReader() promises.push(songObj.lyricsFile.read().then(result => {
promises.push(pageEvents.load(reader).then(event => { songObj.lyricsData = result
songObj.lyricsData = event.target.result }, () => Promise.resolve()), songObj.lyricsFile.path)
}, () => Promise.resolve()), songObj.lyricsFile.webkitRelativePath)
reader.readAsText(songObj.lyricsFile)
} }
Promise.all(promises).then(resolve) Promise.all(promises).then(resolve)
} }

View File

@ -0,0 +1,360 @@
class CustomSongs{
constructor(touchEnabled){
this.touchEnabled = touchEnabled
loader.changePage("customsongs", true)
if(touchEnabled){
this.getElement("view-outer").classList.add("touch-enabled")
}
this.locked = false
this.mode = "main"
var tutorialTitle = this.getElement("view-title")
this.setAltText(tutorialTitle, strings.customSongs.title)
var tutorialContent = this.getElement("view-content")
strings.customSongs.description.forEach(string => {
tutorialContent.appendChild(document.createTextNode(string))
tutorialContent.appendChild(document.createElement("br"))
})
this.items = []
this.linkLocalFolder = document.getElementById("link-localfolder")
this.hasLocal = "webkitdirectory" in HTMLInputElement.prototype && !(/Android/.test(navigator.userAgent))
if(this.hasLocal){
this.browse = document.getElementById("browse")
pageEvents.add(this.browse, "change", this.browseChange.bind(this))
this.setAltText(this.linkLocalFolder, strings.customSongs.localFolder)
pageEvents.add(this.linkLocalFolder, ["mousedown", "touchstart"], this.localFolder.bind(this))
this.items.push(this.linkLocalFolder)
}else{
this.linkLocalFolder.parentNode.removeChild(this.linkLocalFolder)
}
this.linkGdriveFolder = document.getElementById("link-gdrivefolder")
if(gameConfig.google_credentials.gdrive_enabled){
this.setAltText(this.linkGdriveFolder, strings.customSongs.gdriveFolder)
pageEvents.add(this.linkGdriveFolder, ["mousedown", "touchstart"], this.gdriveFolder.bind(this))
this.items.push(this.linkGdriveFolder)
}else{
this.linkGdriveFolder.parentNode.removeChild(this.linkGdriveFolder)
}
this.endButton = this.getElement("view-end-button")
this.setAltText(this.endButton, strings.session.cancel)
pageEvents.add(this.endButton, ["mousedown", "touchstart"], event => this.onEnd(event, true))
this.items.push(this.endButton)
this.selected = this.items.length - 1
this.loaderDiv = document.createElement("div")
this.loaderDiv.innerHTML = assets.pages["loadsong"]
var loadingText = this.loaderDiv.querySelector("#loading-text")
this.setAltText(loadingText, strings.loading)
if(DataTransferItem.prototype.webkitGetAsEntry){
this.dropzone = document.getElementById("dropzone")
var dropContent = this.dropzone.getElementsByClassName("view-content")[0]
dropContent.innerText = strings.customSongs.dropzone
this.dragging = false
pageEvents.add(document, "dragover", event => {
event.preventDefault()
if(!this.locked){
event.dataTransfer.dropEffect = "copy"
this.dropzone.classList.add("dragover")
this.dragging = true
}else{
event.dataTransfer.dropEffect = "none"
}
})
pageEvents.add(document, "dragleave", () => {
this.dropzone.classList.remove("dragover")
this.dragging = false
})
pageEvents.add(document, "drop", this.filesDropped.bind(this))
}
this.errorDiv = document.getElementById("customsongs-error")
pageEvents.add(this.errorDiv, ["mousedown", "touchstart"], event => {
if(event.target === event.currentTarget){
this.hideError()
}
})
var errorTitle = this.errorDiv.getElementsByClassName("view-title")[0]
this.setAltText(errorTitle, strings.customSongs.importError)
this.errorContent = this.errorDiv.getElementsByClassName("view-content")[0]
this.errorEnd = this.errorDiv.getElementsByClassName("view-end-button")[0]
this.setAltText(this.errorEnd, strings.tutorial.ok)
pageEvents.add(this.errorEnd, ["mousedown", "touchstart"], () => this.hideError(true))
this.keyboard = new Keyboard({
confirm: ["enter", "space", "don_l", "don_r"],
previous: ["left", "up", "ka_l"],
next: ["right", "down", "ka_r"],
backEsc: ["escape"]
}, this.keyPressed.bind(this))
this.gamepad = new Gamepad({
confirmPad: ["b", "ls", "rs"],
previous: ["u", "l", "lb", "lt", "lsu", "lsl"],
next: ["d", "r", "rb", "rt", "lsd", "lsr"],
back: ["start", "a"]
}, this.keyPressed.bind(this))
pageEvents.send("custom-songs")
}
getElement(name){
return loader.screen.getElementsByClassName(name)[0]
}
setAltText(element, text){
element.innerText = text
element.setAttribute("alt", text)
}
localFolder(event){
if(event){
if(event.type === "touchstart"){
event.preventDefault()
}else if(event.which !== 1){
return
}
}
if(this.locked || this.mode !== "main"){
return
}
this.changeSelected(this.linkLocalFolder)
this.browse.click()
}
browseChange(event){
var files = []
for(var i = 0; i < event.target.files.length; i++){
files.push(new LocalFile(event.target.files[i]))
}
this.importLocal(files)
}
filesDropped(event){
event.preventDefault()
this.dropzone.classList.remove("dragover")
this.dragging = false
if(this.locked){
return
}
var files = []
var walk = (entry, path="") => {
return new Promise(resolve => {
if(entry.isFile){
entry.file(file => {
files.push(new LocalFile(file, path + file.name))
return resolve()
}, resolve)
}else if(entry.isDirectory){
var dirReader = entry.createReader()
dirReader.readEntries(entries => {
var dirPromises = []
for(var i = 0; i < entries.length; i++){
dirPromises.push(walk(entries[i], path + entry.name + "/"))
}
return Promise.all(dirPromises).then(resolve)
}, resolve)
}else{
return resolve()
}
})
}
var dropPromises = []
for(var i = 0; i < event.dataTransfer.items.length; i++){
var entry = event.dataTransfer.items[i].webkitGetAsEntry()
if(entry){
dropPromises.push(walk(entry))
}
}
Promise.all(dropPromises).then(() => this.importLocal(files))
}
importLocal(files){
if(!files.length){
return
}
this.locked = true
this.loading(true)
var importSongs = new ImportSongs()
importSongs.load(files).then(this.songsLoaded.bind(this), e => {
this.browse.parentNode.reset()
this.locked = false
this.loading(false)
if(e === "nosongs"){
this.showError(strings.customSongs.noSongs)
}else if(e !== "cancel"){
return Promise.reject(e)
}
})
}
gdriveFolder(event){
if(event){
if(event.type === "touchstart"){
event.preventDefault()
}else if(event.which !== 1){
return
}
}
if(this.locked || this.mode !== "main"){
return
}
this.changeSelected(this.linkGdriveFolder)
this.locked = true
this.loading(true)
var importSongs = new ImportSongs(true)
if(!gpicker){
var gpickerPromise = loader.loadScript("/src/js/gpicker.js").then(() => {
gpicker = new Gpicker()
})
}else{
var gpickerPromise = Promise.resolve()
}
gpickerPromise.then(() => {
return gpicker.browse(locked => {
this.locked = locked
this.loading(locked)
}, error => {
this.showError(error)
})
}).then(files => importSongs.load(files))
.then(this.songsLoaded.bind(this))
.catch(e => {
this.locked = false
this.loading(false)
if(e === "nosongs"){
this.showError(strings.customSongs.noSongs)
}else if(e !== "cancel"){
return Promise.reject(e)
}
})
}
loading(show){
if(show){
loader.screen.appendChild(this.loaderDiv)
}else{
loader.screen.removeChild(this.loaderDiv)
}
}
songsLoaded(songs){
if(songs){
var length = songs.length
assets.songs = songs
assets.customSongs = true
assets.customSelected = 0
}
assets.sounds["se_don"].play()
this.clean()
setTimeout(() => {
new SongSelect("customSongs", false, this.touchEnabled)
pageEvents.send("import-songs", length)
}, 500)
}
keyPressed(pressed, name){
if(!pressed || this.locked){
return
}
var selected = this.items[this.selected]
if(this.mode === "main"){
if(name === "confirm" || name === "confirmPad"){
if(selected === this.endButton){
this.onEnd(null, true)
}else if(name !== "confirmPad"){
if(selected === this.linkLocalFolder){
assets.sounds["se_don"].play()
this.localFolder()
}else if(selected === this.linkGdriveFolder){
assets.sounds["se_don"].play()
this.gdriveFolder()
}
}
}else if(name === "previous" || name === "next"){
selected.classList.remove("selected")
this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1))
this.items[this.selected].classList.add("selected")
assets.sounds["se_ka"].play()
}else if(name === "back" || name === "backEsc"){
if(!this.dragging || name !== "backEsc"){
this.onEnd()
}
}
}else if(this.mode === "error"){
if(name === "confirm" || name === "confirmPad" || name === "back" || name === "backEsc"){
this.hideError(name === "confirm" || name === "confirmPad")
}
}
}
changeSelected(button){
var selected = this.items[this.selected]
if(selected !== button){
selected.classList.remove("selected")
this.selected = this.items.findIndex(item => item === button)
this.items[this.selected].classList.add("selected")
}
}
mod(length, index){
return ((index % length) + length) % length
}
onEnd(event, confirm){
if(this.locked || this.mode !== "main"){
return
}
var touched = false
if(event){
if(event.type === "touchstart"){
event.preventDefault()
touched = true
}else if(event.which !== 1){
return
}
}else{
touched = this.touchEnabled
}
this.clean()
assets.sounds[confirm ? "se_don" : "se_cancel"].play()
setTimeout(() => {
new SongSelect("customSongs", false, touched)
}, 500)
}
showError(text){
if(this.mode === "error"){
return
}
this.mode = "error"
this.errorContent.innerText = text
this.errorDiv.style.display = "flex"
assets.sounds["se_pause"].play()
}
hideError(confirm){
if(this.mode !== "error"){
return
}
this.mode = "main"
this.errorDiv.style.display = ""
assets.sounds[confirm ? "se_don" : "se_cancel"].play()
}
clean(){
this.keyboard.clean()
this.gamepad.clean()
pageEvents.remove(this.browse, "change")
if(this.hasLocal){
pageEvents.remove(this.linkLocalFolder, ["mousedown", "touchstart"])
}
if(gameConfig.google_credentials.gdrive_enabled){
pageEvents.remove(this.linkGdriveFolder, ["mousedown", "touchstart"])
}
pageEvents.remove(this.endButton, ["mousedown", "touchstart"])
pageEvents.remove(this.errorDiv, ["mousedown", "touchstart"])
pageEvents.remove(this.errorEnd, ["mousedown", "touchstart"])
if(DataTransferItem.prototype.webkitGetAsEntry){
pageEvents.remove(document, ["dragover", "dragleave", "drop"])
delete this.dropzone
}
delete this.browse
delete this.linkLocalFolder
delete this.linkGdriveFolder
delete this.endButton
delete this.items
delete this.loaderDiv
delete this.errorDiv
delete this.errorContent
delete this.errorEnd
}
}

257
public/src/js/gpicker.js Normal file
View File

@ -0,0 +1,257 @@
class Gpicker{
constructor(){
this.apiKey = gameConfig.google_credentials.api_key
this.oauthClientId = gameConfig.google_credentials.oauth_client_id
this.projectNumber = gameConfig.google_credentials.project_number
this.scope = "https://www.googleapis.com/auth/drive.readonly"
this.folder = "application/vnd.google-apps.folder"
this.filesUrl = "https://www.googleapis.com/drive/v3/files/"
this.resolveQueue = []
this.queueActive = false
}
browse(lockedCallback, errorCallback){
return this.loadApi()
.then(() => this.getToken(lockedCallback, errorCallback))
.then(() => new Promise((resolve, reject) => {
this.displayPicker(data => {
if(data.action === "picked"){
var file = data.docs[0]
var folders = []
var rateLimit = -1
var lastBatch = 0
var walk = (files, output=[]) => {
for(var i = 0; i < files.length; i++){
var path = files[i].path ? files[i].path + "/" : ""
var list = files[i].list
if(!list){
continue
}
for(var j = 0; j < list.length; j++){
var file = list[j]
if(file.mimeType === this.folder){
folders.push({
path: path + file.name,
id: file.id
})
}else{
output.push(new GdriveFile({
path: path + file.name,
name: file.name,
id: file.id
}))
}
}
}
var batchList = []
for(var i = 0; i < folders.length && batchList.length < 100; i++){
if(!folders[i].listed){
folders[i].pos = i
folders[i].listed = true
batchList.push(folders[i])
}
}
if(batchList.length){
var batch = gapi.client.newBatch()
batchList.forEach(folder => {
var req = {
q: "'" + folder.id + "' in parents and trashed = false",
orderBy: "name_natural"
}
if(folder.pageToken){
req.pageToken = folder.pageToken
}
batch.add(gapi.client.drive.files.list(req), {id: folder.pos})
})
if(lastBatch + batchList.length > 100){
var waitPromise = this.sleep(1000)
}else{
var waitPromise = Promise.resolve()
}
return waitPromise.then(() => this.queue()).then(() => batch.then(responses => {
var files = []
var rateLimited = false
for(var i in responses.result){
var result = responses.result[i].result
if(result.error){
if(result.error.errors[0].domain !== "usageLimits"){
console.warn(result)
}else if(!rateLimited){
rateLimited = true
rateLimit++
folders.push({
path: folders[i].path,
id: folders[i].id,
pageToken: folders[i].pageToken
})
}
}else{
if(result.nextPageToken){
folders.push({
path: folders[i].path,
id: folders[i].id,
pageToken: result.nextPageToken
})
}
files.push({path: folders[i].path, list: result.files})
}
}
if(rateLimited){
return this.sleep(Math.pow(2, rateLimit) * 1000).then(() => walk(files, output))
}else{
return walk(files, output)
}
}))
}else{
return output
}
}
if(file.mimeType === this.folder){
return walk([{list: [file]}]).then(resolve, reject)
}else{
return reject("cancel")
}
}else if(data.action === "cancel"){
return reject("cancel")
}
})
}))
}
loadApi(){
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", {
callback: resolve,
onerror: reject
})
))
.then(() => new Promise((resolve, reject) =>
gapi.client.load("drive", "v3").then(resolve, reject)
))
}
getToken(lockedCallback=()=>{}, errorCallback=()=>{}){
if(this.oauthToken){
return Promise.resolve()
}
if(!this.auth){
var authPromise = gapi.auth2.init({
clientId: this.oauthClientId,
fetch_basic_profile: false,
scope: this.scope
}).then(() => {
this.auth = gapi.auth2.getAuthInstance()
}, e => {
if(e.details){
errorCallback(strings.gpicker.authError.replace("%s", e.details))
}
return Promise.reject(e)
})
}else{
var authPromise = Promise.resolve()
}
return authPromise.then(() => {
var user = this.auth.currentUser.get()
if(!this.checkScope(user)){
lockedCallback(false)
this.auth.signIn().then(user => {
if(this.checkScope(user)){
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
}
}
displayPicker(callback){
var picker = gapi.picker.api
new picker.PickerBuilder()
.setDeveloperKey(this.apiKey)
.setAppId(this.projectNumber)
.setOAuthToken(this.oauthToken)
.setLocale(strings.gpicker.locale)
.hideTitleBar()
.addView(new picker.DocsView("folders")
.setLabel(strings.gpicker.myDrive)
.setParent("root")
.setSelectFolderEnabled(true)
.setMode("grid")
)
.addView(new picker.DocsView("folders")
.setLabel(strings.gpicker.starred)
.setStarred(true)
.setSelectFolderEnabled(true)
.setMode("grid")
)
.addView(new picker.DocsView("folders")
.setLabel(strings.gpicker.sharedWithMe)
.setOwnedByMe(false)
.setSelectFolderEnabled(true)
.setMode("list")
)
.setCallback(callback)
.setSize(Infinity, Infinity)
.build()
.setVisible(true)
}
downloadFile(id, arrayBuffer, 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"
}
request.setRequestHeader("Authorization", "Bearer " + this.oauthToken)
}, true).then(event => {
var request = event.target
var reject = () => Promise.reject(`${url} (${request.status})`)
if(request.status === 200){
return request.response
}else if(request.status === 401 && !retry){
return new Response(request.response).json().then(response => {
var e = response.error
if(e && e.errors[0].reason === "authError"){
delete this.oauthToken
return this.downloadFile(id, arrayBuffer, true)
}else{
return reject()
}
}, reject)
}
return reject()
})
)
}
sleep(time){
return new Promise(resolve => setTimeout(resolve, time))
}
queue(){
return new Promise(resolve => {
this.resolveQueue.push(resolve)
if(!this.queueActive){
this.queueActive = true
this.queueTimer = setInterval(this.parseQueue.bind(this), 100)
this.parseQueue()
}
})
}
parseQueue(){
if(this.resolveQueue.length){
var resolve = this.resolveQueue.shift()
resolve()
}else{
this.queueActive = false
clearInterval(this.queueTimer)
}
}
}

View File

@ -1,35 +1,13 @@
class ImportSongs{ class ImportSongs{
constructor(songSelect, event){ constructor(limited, otherFiles){
this.songSelect = songSelect this.limited = limited
this.songSelect.redrawRunning = false
this.songSelect.pointer(false)
this.loaderDiv = document.createElement("div")
this.loaderDiv.innerHTML = assets.pages["loadsong"]
loader.screen.appendChild(this.loaderDiv)
var loadingText = document.getElementById("loading-text")
loadingText.appendChild(document.createTextNode(strings.loading))
loadingText.setAttribute("alt", strings.loading)
var files = []
for(var i = 0; i < event.target.files.length; i++){
files.push(event.target.files[i])
}
var extensionRegex = /\.[^\/]+$/
files.sort((a, b) => {
var path1 = a.webkitRelativePath.replace(extensionRegex, "")
var path2 = b.webkitRelativePath.replace(extensionRegex, "")
return path1 > path2 ? 1 : -1
})
this.tjaFiles = [] this.tjaFiles = []
this.osuFiles = [] this.osuFiles = []
this.assetFiles = {} this.assetFiles = {}
var metaFiles = [] this.otherFiles = otherFiles || {}
this.otherFiles = {}
this.songs = [] this.songs = []
this.stylesheet = [] this.stylesheet = []
this.songTitle = {} this.songTitle = this.otherFiles.songTitle || {}
this.uraRegex = /\s*[\(]裏[\)]$/ this.uraRegex = /\s*[\(]裏[\)]$/
this.courseTypes = { this.courseTypes = {
"easy": 0, "easy": 0,
@ -38,7 +16,6 @@
"oni": 3, "oni": 3,
"ura": 4 "ura": 4
} }
this.categoryAliases = {} this.categoryAliases = {}
assets.categories.forEach(cat => { assets.categories.forEach(cat => {
this.categoryAliases[cat.title.toLowerCase()] = cat.id this.categoryAliases[cat.title.toLowerCase()] = cat.id
@ -53,7 +30,6 @@
} }
} }
}) })
this.assetSelectors = { this.assetSelectors = {
"bg-pattern-1": ".pattern-bg", "bg-pattern-1": ".pattern-bg",
"bg_genre_0": "#song-select", "bg_genre_0": "#song-select",
@ -66,11 +42,21 @@
"bg_stage_2": ".song-stage-2", "bg_stage_2": ".song-stage-2",
"bg_stage_3": ".song-stage-3" "bg_stage_3": ".song-stage-3"
} }
this.comboVoices = ["v_combo_50"].concat(Array.from(Array(50), (d, i) => "v_combo_" + ((i + 1) * 100)))
}
load(files){
var extensionRegex = /\.[^\/]+$/
files.sort((a, b) => {
var path1 = a.path.replace(extensionRegex, "")
var path2 = b.path.replace(extensionRegex, "")
return path1 > path2 ? 1 : -1
})
var metaFiles = []
for(var i = 0; i < files.length; i++){ for(var i = 0; i < files.length; i++){
var file = files[i] var file = files[i]
var name = file.name.toLowerCase() var name = file.name.toLowerCase()
var path = file.webkitRelativePath.toLowerCase() var path = file.path.toLowerCase()
if(name.endsWith(".tja")){ if(name.endsWith(".tja")){
this.tjaFiles.push({ this.tjaFiles.push({
file: file, file: file,
@ -81,13 +67,13 @@
file: file, file: file,
index: i index: i
}) })
}else if(name === "genre.ini" || name === "box.def" || name === "songtitle.txt"){ }else if(!this.limited && (name === "genre.ini" || name === "box.def") || name === "songtitle.txt"){
var level = (file.webkitRelativePath.match(/\//g) || []).length var level = (file.path.match(/\//g) || []).length
metaFiles.push({ metaFiles.push({
file: file, file: file,
level: (level * 2) + (name === "genre.ini" ? 1 : 0) level: (level * 2) + (name === "genre.ini" ? 1 : 0)
}) })
}else if(path.indexOf("/taiko-web assets/") !== -1){ }else if(!this.limited && (path.indexOf("/taiko-web assets/") !== -1 || path.indexOf("taiko-web assets/") === 0)){
if(!(name in this.assetFiles)){ if(!(name in this.assetFiles)){
this.assetFiles[name] = file this.assetFiles[name] = file
} }
@ -97,32 +83,30 @@
} }
var metaPromises = [] var metaPromises = []
metaFiles.forEach(fileObj => { metaFiles.forEach(fileObj => {
metaPromises.push(this.addMeta(fileObj)) metaPromises.push(this.addMeta(fileObj))
}) })
Promise.all(metaPromises).then(() => { return Promise.all(metaPromises).then(() => {
var songPromises = [] var songPromises = []
this.tjaFiles.forEach(fileObj => { this.tjaFiles.forEach(fileObj => {
songPromises.push(this.addTja(fileObj)) songPromises.push(this.addTja(fileObj).catch(e => console.warn(e)))
}) })
this.osuFiles.forEach(fileObj => { this.osuFiles.forEach(fileObj => {
songPromises.push(this.addOsu(fileObj)) songPromises.push(this.addOsu(fileObj).catch(e => console.warn(e)))
}) })
songPromises.push(this.addAssets()) songPromises.push(this.addAssets())
Promise.all(songPromises).then(this.loaded.bind(this)) return Promise.all(songPromises)
}) }).then(this.loaded.bind(this))
} }
addMeta(fileObj){ addMeta(fileObj){
var file = fileObj.file var file = fileObj.file
var level = fileObj.level var level = fileObj.level
var name = file.name.toLowerCase() var name = file.name.toLowerCase()
var reader = new FileReader() return file.read(name === "songtitle.txt" ? undefined : "sjis").then(data => {
var promise = pageEvents.load(reader).then(event => { var data = data.replace(/\0/g, "").split("\n")
var data = event.target.result.replace(/\0/g, "").split("\n")
var category var category
if(name === "genre.ini"){ if(name === "genre.ini"){
var key var key
@ -177,9 +161,9 @@
} }
} }
if(category){ if(category){
var metaPath = file.webkitRelativePath.toLowerCase().slice(0, file.name.length * -1) var metaPath = file.path.toLowerCase().slice(0, file.name.length * -1)
var filesLoop = fileObj => { var filesLoop = fileObj => {
var tjaPath = fileObj.file.webkitRelativePath.toLowerCase().slice(0, fileObj.file.name.length * -1) var tjaPath = fileObj.file.path.toLowerCase().slice(0, fileObj.file.name.length * -1)
if(tjaPath.startsWith(metaPath) && (!("categoryLevel" in fileObj) || fileObj.categoryLevel < level)){ if(tjaPath.startsWith(metaPath) && (!("categoryLevel" in fileObj) || fileObj.categoryLevel < level)){
if(category.toLowerCase() in this.categoryAliases){ if(category.toLowerCase() in this.categoryAliases){
fileObj.category_id = this.categoryAliases[category.toLowerCase()] fileObj.category_id = this.categoryAliases[category.toLowerCase()]
@ -192,13 +176,9 @@
this.tjaFiles.forEach(filesLoop) this.tjaFiles.forEach(filesLoop)
this.osuFiles.forEach(filesLoop) this.osuFiles.forEach(filesLoop)
} }
}).catch(() => {}) }).catch(e => {
if(name === "songtitle.txt"){ console.warn(e)
reader.readAsText(file) })
}else{
reader.readAsText(file, "sjis")
}
return promise
} }
addTja(fileObj){ addTja(fileObj){
@ -206,24 +186,33 @@
var index = fileObj.index var index = fileObj.index
var category = fileObj.category var category = fileObj.category
var category_id = fileObj.category_id var category_id = fileObj.category_id
var reader = new FileReader() if(!this.limited){
var promise = pageEvents.load(reader).then(event => { var filePromise = file.read("sjis")
var data = event.target.result.replace(/\0/g, "").split("\n") }else{
var filePromise = Promise.resolve()
}
return filePromise.then(dataRaw => {
var data = dataRaw ? dataRaw.replace(/\0/g, "").split("\n") : []
var tja = new ParseTja(data, "oni", 0, 0, true) var tja = new ParseTja(data, "oni", 0, 0, true)
var songObj = { var songObj = {
id: index + 1, id: index + 1,
order: index + 1, order: index + 1,
title: file.name.slice(0, file.name.lastIndexOf(".")),
type: "tja", type: "tja",
chart: file, chart: file,
courses: {}, courses: {},
music: "muted" music: "muted",
custom: true
}
if(this.limited){
songObj.unloaded = true
} }
var coursesAdded = false var coursesAdded = false
var titleLang = {} var titleLang = {}
var titleLangAdded = false var titleLangAdded = false
var subtitleLangAdded = false var subtitleLangAdded = false
var subtitleLang = {} var subtitleLang = {}
var dir = file.webkitRelativePath.toLowerCase() var dir = file.path.toLowerCase()
dir = dir.slice(0, dir.lastIndexOf("/") + 1) dir = dir.slice(0, dir.lastIndexOf("/") + 1)
for(var diff in tja.metadata){ for(var diff in tja.metadata){
var meta = tja.metadata[diff] var meta = tja.metadata[diff]
@ -321,19 +310,19 @@
songObj.category_id = category_id songObj.category_id = category_id
} }
} }
if(coursesAdded){ if(coursesAdded || songObj.unloaded){
this.songs[index] = songObj this.songs[index] = songObj
} }
var hash = md5.base64(event.target.result).slice(0, -2) if(!this.limited){
var hash = md5.base64(dataRaw).slice(0, -2)
songObj.hash = hash songObj.hash = hash
scoreStorage.songTitles[songObj.title] = hash scoreStorage.songTitles[songObj.title] = hash
var score = scoreStorage.get(hash, false, true) var score = scoreStorage.get(hash, false, true)
if(score){ if(score){
score.title = songObj.title score.title = songObj.title
} }
}).catch(() => {}) }
reader.readAsText(file, "sjis") })
return promise
} }
addOsu(fileObj){ addOsu(fileObj){
@ -341,11 +330,15 @@
var index = fileObj.index var index = fileObj.index
var category = fileObj.category var category = fileObj.category
var category_id = fileObj.category_id var category_id = fileObj.category_id
var reader = new FileReader() if(!this.limited){
var promise = pageEvents.load(reader).then(event => { var filePromise = file.read()
var data = event.target.result.replace(/\0/g, "").split("\n") }else{
var osu = new ParseOsu(data, "oni", 0, 0, true); var filePromise = Promise.resolve()
var dir = file.webkitRelativePath.toLowerCase() }
return filePromise.then(dataRaw => {
var data = dataRaw ? dataRaw.replace(/\0/g, "").split("\n") : []
var osu = new ParseOsu(data, "oni", 0, 0, true)
var dir = file.path.toLowerCase()
dir = dir.slice(0, dir.lastIndexOf("/") + 1) dir = dir.slice(0, dir.lastIndexOf("/") + 1)
var songObj = { var songObj = {
id: index + 1, id: index + 1,
@ -356,14 +349,17 @@
subtitle_lang: { subtitle_lang: {
en: osu.metadata.Artist || osu.metadata.ArtistUnicode en: osu.metadata.Artist || osu.metadata.ArtistUnicode
}, },
preview: osu.generalInfo.PreviewTime / 1000, preview: osu.generalInfo.PreviewTime ? osu.generalInfo.PreviewTime / 1000 : 0,
courses: { courses: {},
oni:{ music: (osu.generalInfo.AudioFilename ? this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] : "") || "muted"
}
if(!this.limited){
songObj.courses.oni = {
stars: parseInt(osu.difficulty.overallDifficulty) || 0, stars: parseInt(osu.difficulty.overallDifficulty) || 0,
branch: false branch: false
} }
}, }else{
music: this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] || "muted" songObj.unloaded = true
} }
var filename = file.name.slice(0, file.name.lastIndexOf(".")) var filename = file.name.slice(0, file.name.lastIndexOf("."))
var title = osu.metadata.TitleUnicode || osu.metadata.Title || file.name.slice(0, file.name.lastIndexOf(".")) var title = osu.metadata.TitleUnicode || osu.metadata.Title || file.name.slice(0, file.name.lastIndexOf("."))
@ -375,7 +371,7 @@
} }
songObj.title = title + suffix songObj.title = title + suffix
songObj.title_lang = { songObj.title_lang = {
en: (osu.metadata.Title || osu.metadata.TitleUnicode) + suffix en: (osu.metadata.Title || osu.metadata.TitleUnicode || title) + suffix
} }
}else{ }else{
songObj.title = filename songObj.title = filename
@ -389,32 +385,30 @@
}else{ }else{
songObj.category_id = category_id songObj.category_id = category_id
} }
var hash = md5.base64(event.target.result).slice(0, -2) if(!this.limited){
var hash = md5.base64(dataRaw).slice(0, -2)
songObj.hash = hash songObj.hash = hash
scoreStorage.songTitles[songObj.title] = hash scoreStorage.songTitles[songObj.title] = hash
var score = scoreStorage.get(hash, false, true) var score = scoreStorage.get(hash, false, true)
if(score){ if(score){
score.title = songObj.title score.title = songObj.title
} }
}).catch(() => {}) }
reader.readAsText(file) })
return promise
} }
addAssets(){ addAssets(){
return new Promise((resolve, reject) => {
var promises = [] var promises = []
for(let name in this.assetFiles){ for(let name in this.assetFiles){
let id = this.getFilename(name) let id = this.getFilename(name)
var file = this.assetFiles[name] var file = this.assetFiles[name]
var index = name.lastIndexOf(".")
if(name === "vectors.json"){ if(name === "vectors.json"){
var reader = new FileReader() promises.push(file.read().then(response => {
promises.push(pageEvents.load(reader).then(() => response => {
vectors = JSON.parse(response) vectors = JSON.parse(response)
})) }))
reader.readAsText(file)
} }
if(assets.img.indexOf(name) !== -1){ if(name.endsWith(".png")){
let image = document.createElement("img") let image = document.createElement("img")
promises.push(pageEvents.load(image).then(() => { promises.push(pageEvents.load(image).then(() => {
if(id in this.assetSelectors){ if(id in this.assetSelectors){
@ -423,9 +417,15 @@
} }
})) }))
image.id = name image.id = name
image.src = URL.createObjectURL(file) promises.push(file.blob().then(blob => {
image.src = URL.createObjectURL(blob)
}))
loader.assetsDiv.appendChild(image) loader.assetsDiv.appendChild(image)
assets.image[id].parentNode.removeChild(assets.image[id]) var oldImage = assets.image[id]
if(oldImage && oldImage.parentNode){
URL.revokeObjectURL(oldImage.src)
oldImage.parentNode.removeChild(oldImage)
}
assets.image[id] = image assets.image[id] = image
} }
if(assets.audioSfx.indexOf(name) !== -1){ if(assets.audioSfx.indexOf(name) !== -1){
@ -448,13 +448,19 @@
assets.sounds[id].clean() assets.sounds[id].clean()
promises.push(this.loadSound(file, name, snd.sfxLoudGain)) promises.push(this.loadSound(file, name, snd.sfxLoudGain))
} }
if(this.comboVoices.indexOf(id) !== -1){
promises.push(snd.sfxGain.load(file).then(sound => {
assets.sounds[id] = sound
assets.sounds[id + "_p1"] = assets.sounds[id].copy(snd.sfxGainL)
assets.sounds[id + "_p2"] = assets.sounds[id].copy(snd.sfxGainR)
}))
} }
Promise.all(promises).then(resolve, reject) }
}) return Promise.all(promises)
} }
loadSound(file, name, gain){ loadSound(file, name, gain){
var id = this.getFilename(name) var id = this.getFilename(name)
return gain.load(file, true).then(sound => { return gain.load(file).then(sound => {
assets.sounds[id] = sound assets.sounds[id] = sound
}) })
} }
@ -463,7 +469,7 @@
} }
getCategory(file, exclude){ getCategory(file, exclude){
var path = file.webkitRelativePath.toLowerCase().split("/") var path = file.path.toLowerCase().split("/")
for(var i = path.length - 2; i >= 0; i--){ for(var i = path.length - 2; i >= 0; i--){
var hasTitle = false var hasTitle = false
for(var j in exclude){ for(var j in exclude){
@ -532,24 +538,17 @@
document.head.appendChild(style) document.head.appendChild(style)
} }
if(this.songs.length){ if(this.songs.length){
var length = this.songs.length if(this.limited){
assets.songs = this.songs assets.otherFiles = this.otherFiles
assets.customSongs = true assets.otherFiles.songTitle = this.songTitle
assets.customSelected = 0
assets.sounds["se_don"].play()
this.songSelect.clean()
setTimeout(() => {
loader.screen.removeChild(this.loaderDiv)
this.clean()
new SongSelect("browse", false, this.songSelect.touchEnabled)
pageEvents.send("import-songs", length)
}, 500)
}else{
loader.screen.removeChild(this.loaderDiv)
this.songSelect.browse.parentNode.reset()
this.songSelect.redrawRunning = true
this.clean()
} }
return Promise.resolve(this.songs)
}else if(Object.keys(this.assetFiles).length){
return Promise.resolve()
}else{
return Promise.reject("nosongs")
}
this.clean()
} }
joinPath(){ joinPath(){
@ -587,10 +586,10 @@
} }
clean(){ clean(){
delete this.loaderDiv
delete this.songs delete this.songs
delete this.tjaFiles delete this.tjaFiles
delete this.osuFiles delete this.osuFiles
delete this.assetFiles
delete this.otherFiles delete this.otherFiles
} }
} }

View File

@ -25,21 +25,13 @@ class Loader{
this.loaderPercentage = document.querySelector("#loader .percentage") this.loaderPercentage = document.querySelector("#loader .percentage")
this.loaderProgress = document.querySelector("#loader .progress") this.loaderProgress = document.querySelector("#loader .progress")
var queryString = gameConfig._version.commit_short ? "?" + gameConfig._version.commit_short : "" this.queryString = gameConfig._version.commit_short ? "?" + gameConfig._version.commit_short : ""
if(gameConfig.custom_js){ if(gameConfig.custom_js){
var script = document.createElement("script") this.addPromise(this.loadScript(gameConfig.custom_js), gameConfig.custom_js)
var url = gameConfig.custom_js + queryString
this.addPromise(pageEvents.load(script), url)
script.src = url
document.head.appendChild(script)
} }
assets.js.forEach(name => { assets.js.forEach(name => {
var script = document.createElement("script") this.addPromise(this.loadScript("/src/js/" + name), "/src/js/" + name)
var url = "/src/js/" + name + queryString
this.addPromise(pageEvents.load(script), url)
script.src = url
document.head.appendChild(script)
}) })
var pageVersion = versionLink.href var pageVersion = versionLink.href
@ -53,20 +45,19 @@ class Loader{
gameConfig._version.commit && gameConfig._version.commit &&
versionLink.href.indexOf(gameConfig._version.commit) === -1 versionLink.href.indexOf(gameConfig._version.commit) === -1
){ ){
// Version in the config does not match version on the page reject("Version on the page and config does not match\n(page: " + pageVersion + ",\nconfig: "+ gameConfig._version.commit + ")")
reject()
} }
var cssCount = document.styleSheets.length + assets.css.length var cssCount = document.styleSheets.length + assets.css.length
assets.css.forEach(name => { assets.css.forEach(name => {
var stylesheet = document.createElement("link") var stylesheet = document.createElement("link")
stylesheet.rel = "stylesheet" stylesheet.rel = "stylesheet"
stylesheet.href = "/src/css/" + name + queryString stylesheet.href = "/src/css/" + name + this.queryString
document.head.appendChild(stylesheet) document.head.appendChild(stylesheet)
}) })
assets.assetsCss.forEach(name => { assets.assetsCss.forEach(name => {
var stylesheet = document.createElement("link") var stylesheet = document.createElement("link")
stylesheet.rel = "stylesheet" stylesheet.rel = "stylesheet"
stylesheet.href = gameConfig.assets_baseurl + name + queryString stylesheet.href = gameConfig.assets_baseurl + name + this.queryString
document.head.appendChild(stylesheet) document.head.appendChild(stylesheet)
}) })
var checkStyles = () => { var checkStyles = () => {
@ -77,7 +68,7 @@ class Loader{
} }
var interval = setInterval(checkStyles, 100) var interval = setInterval(checkStyles, 100)
checkStyles() checkStyles()
}), "Version on the page and config does not match\n(page: " + pageVersion + ",\nconfig: "+ gameConfig._version.commit + ")") }))
for(var name in assets.fonts){ for(var name in assets.fonts){
var url = gameConfig.assets_baseurl + "fonts/" + assets.fonts[name] var url = gameConfig.assets_baseurl + "fonts/" + assets.fonts[name]
@ -99,7 +90,7 @@ class Loader{
assets.views.forEach(name => { assets.views.forEach(name => {
var id = this.getFilename(name) var id = this.getFilename(name)
var url = "/src/views/" + name + queryString var url = "/src/views/" + name + this.queryString
this.addPromise(this.ajax(url).then(page => { this.addPromise(this.ajax(url).then(page => {
assets.pages[id] = page assets.pages[id] = page
}), url) }), url)
@ -114,7 +105,7 @@ class Loader{
cat.songSkin.infoFill = cat.songSkin.info_fill cat.songSkin.infoFill = cat.songSkin.info_fill
delete cat.songSkin.info_fill delete cat.songSkin.info_fill
} }
}); })
assets.categories.push({ assets.categories.push({
title: "default", title: "default",
@ -127,18 +118,17 @@ class Loader{
}) })
}), "/api/categories") }), "/api/categories")
this.addPromise(this.ajax("/api/songs").then(songs => { var url = gameConfig.assets_baseurl + "img/vectors.json" + this.queryString
assets.songsDefault = JSON.parse(songs)
assets.songs = assets.songsDefault
}), "/api/songs")
var url = gameConfig.assets_baseurl + "img/vectors.json" + queryString
this.addPromise(this.ajax(url).then(response => { this.addPromise(this.ajax(url).then(response => {
vectors = JSON.parse(response) vectors = JSON.parse(response)
}), url) }), url)
this.afterJSCount = this.afterJSCount =
["blurPerformance"].length + [
"/api/songs",
"blurPerformance",
"categories"
].length +
assets.audioSfx.length + assets.audioSfx.length +
assets.audioMusic.length + assets.audioMusic.length +
assets.audioSfxLR.length + assets.audioSfxLR.length +
@ -150,19 +140,49 @@ class Loader{
return return
} }
this.addPromise(this.ajax("/api/songs").then(songs => {
songs = JSON.parse(songs)
songs.forEach(song => {
var directory = gameConfig.songs_baseurl + song.id + "/"
song.music = new RemoteFile(directory + "main.mp3")
if(song.type === "tja"){
song.chart = new RemoteFile(directory + "main.tja")
}else{
song.chart = {separateDiff: true}
for(var diff in song.courses){
if(song.courses[diff]){
song.chart[diff] = new RemoteFile(directory + diff + ".osu")
}
}
}
if(song.lyrics){
song.lyricsFile = new RemoteFile(directory + "main.vtt")
}
if(song.preview > 0){
song.previewMusic = new RemoteFile(directory + "preview.mp3")
}
})
assets.songsDefault = songs
assets.songs = assets.songsDefault
}), "/api/songs")
var categoryPromises = []
assets.categories //load category backgrounds to DOM assets.categories //load category backgrounds to DOM
.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 id = this.getFilename(name)
var image = document.createElement("img") var image = document.createElement("img")
var url = gameConfig.assets_baseurl + "img/" + name var url = gameConfig.assets_baseurl + "img/" + name
this.addPromise(pageEvents.load(image), url) categoryPromises.push(pageEvents.load(image).catch(response => {
this.errorMsg(response, url)
}))
image.id = name image.id = name
image.src = url image.src = url
this.assetsDiv.appendChild(image) this.assetsDiv.appendChild(image)
assets.image[id] = image assets.image[id] = image
}) })
this.addPromise(Promise.all(categoryPromises))
snd.buffer = new SoundBuffer() snd.buffer = new SoundBuffer()
snd.musicGain = snd.buffer.createGain() snd.musicGain = snd.buffer.createGain()
@ -307,7 +327,6 @@ class Loader{
this.promises.push(promise) this.promises.push(promise)
promise.then(this.assetLoaded.bind(this), response => { promise.then(this.assetLoaded.bind(this), response => {
this.errorMsg(response, url) this.errorMsg(response, url)
return Promise.resolve()
}) })
} }
soundUrl(name){ soundUrl(name){
@ -315,7 +334,7 @@ class Loader{
} }
loadSound(name, gain){ loadSound(name, gain){
var id = this.getFilename(name) var id = this.getFilename(name)
return gain.load(this.soundUrl(name)).then(sound => { return gain.load(new RemoteFile(this.soundUrl(name))).then(sound => {
assets.sounds[id] = sound assets.sounds[id] = sound
}) })
} }
@ -414,22 +433,32 @@ 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")
} }
ajax(url, customRequest){ ajax(url, customRequest, customResponse){
return new Promise((resolve, reject) => {
var request = new XMLHttpRequest() var request = new XMLHttpRequest()
request.open("GET", url) request.open("GET", url)
pageEvents.load(request).then(() => { var promise = pageEvents.load(request)
if(!customResponse){
promise = promise.then(() => {
if(request.status === 200){ if(request.status === 200){
resolve(request.response) return request.response
}else{ }else{
reject() return Promise.reject(`${url} (${request.status})`)
}
})
} }
}, reject)
if(customRequest){ if(customRequest){
customRequest(request) customRequest(request)
} }
request.send() request.send()
}) return promise
}
loadScript(url){
var script = document.createElement("script")
var url = url + this.queryString
var promise = pageEvents.load(script)
script.src = url
document.head.appendChild(script)
return promise
} }
getCsrfToken(){ getCsrfToken(){
return this.ajax("api/csrftoken").then(response => { return this.ajax("api/csrftoken").then(response => {

View File

@ -34,10 +34,10 @@ class LoadSong{
run(){ run(){
var song = this.selectedSong var song = this.selectedSong
var id = song.folder var id = song.folder
this.promises = []
if(song.folder !== "calibration"){
assets.sounds["v_start"].play()
var songObj var songObj
this.promises = []
if(id !== "calibration"){
assets.sounds["v_start"].play()
assets.songs.forEach(song => { assets.songs.forEach(song => {
if(song.id === id){ if(song.id === id){
songObj = song songObj = song
@ -50,11 +50,12 @@ class LoadSong{
} }
}) })
}else{ }else{
var songObj = { songObj = {
"music": "muted", music: "muted",
"chart": "blank" custom: true
} }
} }
this.songObj = songObj
song.songBg = this.randInt(1, 5) song.songBg = this.randInt(1, 5)
song.songStage = this.randInt(1, 3) song.songStage = this.randInt(1, 3)
@ -99,15 +100,17 @@ 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.music && (this.imgScale !== 1 || force)){ if(!songObj.custom && (this.imgScale !== 1 || force)){
img.crossOrigin = "Anonymous" img.crossOrigin = "Anonymous"
} }
let promise = pageEvents.load(img) let promise = pageEvents.load(img)
this.addPromise(promise.then(() => { this.addPromise(promise.then(() => {
return this.scaleImg(img, filename, prefix, force) return this.scaleImg(img, filename, prefix, force)
}), songObj.music ? filename + ".png" : skinBase + filename + ".png") }), songObj.custom ? filename + ".png" : skinBase + filename + ".png")
if(songObj.music){ if(songObj.custom){
img.src = URL.createObjectURL(song.songSkin[filename + ".png"]) this.addPromise(song.songSkin[filename + ".png"].blob().then(blob => {
img.src = URL.createObjectURL(blob)
}))
}else{ }else{
img.src = skinBase + filename + ".png" img.src = skinBase + filename + ".png"
} }
@ -115,57 +118,29 @@ class LoadSong{
} }
this.loadSongBg(id) this.loadSongBg(id)
var url = gameConfig.songs_baseurl + id + "/main.mp3"
this.addPromise(new Promise((resolve, reject) => {
if(songObj.sound){ if(songObj.sound){
songObj.sound.gain = snd.musicGain songObj.sound.gain = snd.musicGain
resolve()
}else if(!songObj.music){
snd.musicGain.load(url).then(sound => {
songObj.sound = sound
resolve()
}, reject)
}else if(songObj.music !== "muted"){ }else if(songObj.music !== "muted"){
snd.musicGain.load(songObj.music, true).then(sound => { this.addPromise(snd.musicGain.load(songObj.music).then(sound => {
songObj.sound = sound songObj.sound = sound
resolve() }), songObj.music.url)
}, reject)
}else{
resolve()
} }
}), songObj.music ? songObj.music.webkitRelativePath : url) var chart = songObj.chart
if(songObj.chart){ if(chart.separateDiff){
if(songObj.chart === "blank"){ var chartDiff = this.selectedSong.difficulty
this.songData = "" chart = chart[chartDiff]
}else{
var reader = new FileReader()
this.addPromise(pageEvents.load(reader).then(event => {
this.songData = event.target.result.replace(/\0/g, "").split("\n")
}), songObj.chart.webkitRelativePath)
if(song.type === "tja"){
reader.readAsText(songObj.chart, "sjis")
}else{
reader.readAsText(songObj.chart)
} }
} if(chart){
if(songObj.lyricsFile && settings.getItem("showLyrics")){ this.addPromise(chart.read(song.type === "tja" ? "sjis" : "").then(data => {
var reader = new FileReader()
this.addPromise(pageEvents.load(reader).then(event => {
songObj.lyricsData = event.target.result
}, () => Promise.resolve()), songObj.lyricsFile.webkitRelativePath)
reader.readAsText(songObj.lyricsFile)
}
}else{
var url = this.getSongPath(song)
this.addPromise(loader.ajax(url).then(data => {
this.songData = data.replace(/\0/g, "").split("\n") this.songData = data.replace(/\0/g, "").split("\n")
}), url) }), chart.url)
if(song.lyrics && !songObj.lyricsData && !this.multiplayer && (!this.touchEnabled || this.autoPlayEnabled) && settings.getItem("showLyrics")){ }else{
var url = this.getSongDir(song) + "main.vtt" this.songData = ""
this.addPromise(loader.ajax(url).then(data => {
songObj.lyricsData = data
}), url)
} }
if(songObj.lyricsFile && !songObj.lyricsData && !this.multiplayer && (!this.touchEnabled || this.autoPlayEnabled) && settings.getItem("showLyrics")){
this.addPromise(songObj.lyricsFile.read().then(data => {
songObj.lyricsData = data
}, () => {}), songObj.lyricsFile.url)
} }
if(this.touchEnabled && !assets.image["touch_drum"]){ if(this.touchEnabled && !assets.image["touch_drum"]){
let img = document.createElement("img") let img = document.createElement("img")
@ -289,17 +264,6 @@ class LoadSong{
randInt(min, max){ randInt(min, max){
return Math.floor(Math.random() * (max - min + 1)) + min return Math.floor(Math.random() * (max - min + 1)) + min
} }
getSongDir(selectedSong){
return gameConfig.songs_baseurl + selectedSong.folder + "/"
}
getSongPath(selectedSong){
var directory = this.getSongDir(selectedSong)
if(selectedSong.type === "tja"){
return directory + "main.tja"
}else{
return directory + selectedSong.difficulty + ".osu"
}
}
setupMultiplayer(){ setupMultiplayer(){
var song = this.selectedSong var song = this.selectedSong
@ -326,13 +290,14 @@ class LoadSong{
this.selectedSong2[i] = this.selectedSong[i] this.selectedSong2[i] = this.selectedSong[i]
} }
this.selectedSong2.difficulty = event.value.diff this.selectedSong2.difficulty = event.value.diff
if(song.type === "tja"){ var chart = this.songObj.chart
var chartDiff = this.selectedSong2.difficulty
if(song.type === "tja" || !chart || !chart.separateDiff || !chart[chartDiff]){
this.startMultiplayer() this.startMultiplayer()
}else{ }else{
loader.ajax(this.getSongPath(this.selectedSong2)).then(data => { chart[chartDiff].read(song.type === "tja" ? "sjis" : "").then(data => {
this.song2Data = data.replace(/\0/g, "").split("\n") this.song2Data = data.replace(/\0/g, "").split("\n")
this.startMultiplayer() }, () => {}).then(() => {
}, () => {
this.startMultiplayer() this.startMultiplayer()
}) })
} }
@ -389,6 +354,7 @@ class LoadSong{
} }
clean(){ clean(){
delete this.promises delete this.promises
delete this.songObj
pageEvents.remove(p2, "message") pageEvents.remove(p2, "message")
if(this.cancelButton){ if(this.cancelButton){
pageEvents.remove(this.cancelButton, ["mousedown", "touchstart"]) pageEvents.remove(this.cancelButton, ["mousedown", "touchstart"])

View File

@ -89,6 +89,7 @@ var vectors
var settings var settings
var scoreStorage var scoreStorage
var account = {} var account = {}
var gpicker
pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => { pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => {
if(event.cancelable && cancelTouch && event.target.tagName !== "SELECT"){ if(event.cancelable && cancelTouch && event.target.tagName !== "SELECT"){

View File

@ -49,7 +49,7 @@ class SongSelect{
border: ["#dec4fd", "#a543ef"], border: ["#dec4fd", "#a543ef"],
outline: "#a741ef" outline: "#a741ef"
}, },
"browse": { "customSongs": {
sort: 0, sort: 0,
background: "#fab5d3", background: "#fab5d3",
border: ["#ffe7ef", "#d36aa2"], border: ["#ffe7ef", "#d36aa2"],
@ -80,42 +80,7 @@ class SongSelect{
this.songs = [] this.songs = []
for(let song of assets.songs){ for(let song of assets.songs){
var title = this.getLocalTitle(song.title, song.title_lang) this.songs.push(this.addSong(song))
var subtitle = this.getLocalTitle(title === song.title ? song.subtitle : "", song.subtitle_lang)
var skin = null
var categoryName = ""
var originalCategory = ""
if(song.category_id !== null && song.category_id !== undefined){
var category = assets.categories.find(cat => cat.id === song.category_id)
var categoryName = this.getLocalTitle(category.title, category.title_lang)
var originalCategory = category.title
var skin = this.songSkin[category.title]
}else if(song.category){
var categoryName = song.category
var originalCategory = song.category
}
this.songs.push({
id: song.id,
title: title,
originalTitle: song.title,
subtitle: subtitle,
skin: skin || this.songSkin.default,
courses: song.courses,
originalCategory: originalCategory,
category: categoryName,
category_id: song.category_id,
preview: song.preview || 0,
type: song.type,
offset: song.offset,
songSkin: song.song_skin || {},
music: song.music,
volume: song.volume,
maker: song.maker,
canJump: true,
hash: song.hash || song.title,
order: song.order,
lyrics: song.lyrics
})
} }
this.songs.sort((a, b) => { this.songs.sort((a, b) => {
var catA = a.originalCategory in this.songSkin ? this.songSkin[a.originalCategory] : this.songSkin.default var catA = a.originalCategory in this.songSkin ? this.songSkin[a.originalCategory] : this.songSkin.default
@ -170,17 +135,26 @@ class SongSelect{
action: "settings", action: "settings",
category: strings.random category: strings.random
}) })
if("webkitdirectory" in HTMLInputElement.prototype && !(/Android|iPhone|iPad/.test(navigator.userAgent))){
this.browse = document.getElementById("browse")
pageEvents.add(this.browse, "change", this.browseChange.bind(this))
var showCustom = false
if(gameConfig.google_credentials.gdrive_enabled){
if(!(/iPhone|iPad/.test(navigator.userAgent))){
showCustom = true
}
}else{
if("webkitdirectory" in HTMLInputElement.prototype && !(/Android|iPhone|iPad/.test(navigator.userAgent))){
showCustom = true
}
}
if(showCustom){
this.songs.push({ this.songs.push({
title: assets.customSongs ? strings.defaultSongList : strings.browse, title: assets.customSongs ? strings.customSongs.default : strings.customSongs.title,
skin: this.songSkin.browse, skin: this.songSkin.customSongs,
action: "browse", action: "customSongs",
category: strings.random category: strings.random
}) })
} }
this.songs.push({ this.songs.push({
title: strings.back, title: strings.back,
skin: this.songSkin.back, skin: this.songSkin.back,
@ -501,11 +475,11 @@ class SongSelect{
event.preventDefault() event.preventDefault()
if(this.state.screen === "song" && this.redrawRunning){ if(this.state.screen === "song" && this.redrawRunning){
var currentSong = this.songs[this.selectedSong] var currentSong = this.songs[this.selectedSong]
if(currentSong.action === "browse"){ if(currentSong.action === "customSongs"){
var mouse = this.mouseOffset(event.changedTouches[0].pageX, event.changedTouches[0].pageY) var mouse = this.mouseOffset(event.changedTouches[0].pageX, event.changedTouches[0].pageY)
var moveBy = this.songSelMouse(mouse.x, mouse.y) var moveBy = this.songSelMouse(mouse.x, mouse.y)
if(moveBy === 0){ if(moveBy === 0){
this.toBrowse() this.toCustomSongs()
} }
} }
} }
@ -612,7 +586,7 @@ class SongSelect{
}) })
} }
}else if(this.state.locked !== 1 || fromP2){ }else if(this.state.locked !== 1 || fromP2){
if(this.songs[this.selectedSong].courses && (this.state.locked === 0 || fromP2)){ if(this.songs[this.selectedSong].courses && !this.songs[this.selectedSong].unloaded && (this.state.locked === 0 || fromP2)){
this.state.moveMS = ms this.state.moveMS = ms
}else{ }else{
this.state.moveMS = ms - this.songSelecting.speed * this.songSelecting.resize this.state.moveMS = ms - this.songSelecting.speed * this.songSelecting.resize
@ -668,10 +642,6 @@ class SongSelect{
} }
} }
browseChange(event){
new ImportSongs(this, event)
}
toSelectDifficulty(fromP2){ toSelectDifficulty(fromP2){
var currentSong = this.songs[this.selectedSong] var currentSong = this.songs[this.selectedSong]
if(p2.session && !fromP2 && currentSong.action !== "random"){ if(p2.session && !fromP2 && currentSong.action !== "random"){
@ -686,6 +656,9 @@ class SongSelect{
} }
}else if(this.state.locked === 0 || fromP2){ }else if(this.state.locked === 0 || fromP2){
if(currentSong.courses){ if(currentSong.courses){
if(currentSong.unloaded){
return
}
this.state.screen = "difficulty" this.state.screen = "difficulty"
this.state.screenMS = this.getMS() this.state.screenMS = this.getMS()
this.state.locked = true this.state.locked = true
@ -721,8 +694,8 @@ class SongSelect{
this.toAbout() this.toAbout()
}else if(currentSong.action === "settings"){ }else if(currentSong.action === "settings"){
this.toSettings() this.toSettings()
}else if(currentSong.action === "browse"){ }else if(currentSong.action === "customSongs"){
this.toBrowse() this.toCustomSongs()
} }
} }
this.pointer(false) this.pointer(false)
@ -857,18 +830,25 @@ class SongSelect{
}, 500) }, 500)
} }
} }
toBrowse(){ toCustomSongs(){
if(assets.customSongs){ if(assets.customSongs){
assets.customSongs = false assets.customSongs = false
assets.songs = assets.songsDefault assets.songs = assets.songsDefault
delete assets.otherFiles
this.playSound("se_don") this.playSound("se_don")
this.clean() this.clean()
setTimeout(() => { setTimeout(() => {
new SongSelect("browse", false, this.touchEnabled) new SongSelect("customSongs", false, this.touchEnabled)
}, 500) }, 500)
pageEvents.send("import-songs-default") pageEvents.send("import-songs-default")
}else{ }else{
this.browse.click() localStorage["selectedSong"] = this.selectedSong
this.playSound("se_don")
this.clean()
setTimeout(() => {
new CustomSongs(this.touchEnabled)
}, 500)
} }
} }
@ -1110,7 +1090,7 @@ class SongSelect{
} }
if(screen === "song"){ if(screen === "song"){
if(this.songs[this.selectedSong].courses){ if(this.songs[this.selectedSong].courses && !this.songs[this.selectedSong].unloaded){
selectedWidth = this.songAsset.selectedWidth selectedWidth = this.songAsset.selectedWidth
} }
@ -1311,7 +1291,7 @@ class SongSelect{
highlight = 1 highlight = 1
} }
var selectedSkin = this.songSkin.selected var selectedSkin = this.songSkin.selected
if(screen === "title" || screen === "titleFadeIn" || this.state.locked === 3){ if(screen === "title" || screen === "titleFadeIn" || this.state.locked === 3 || currentSong.unloaded){
selectedSkin = currentSong.skin selectedSkin = currentSong.skin
highlight = 2 highlight = 2
}else if(songSelMoving){ }else if(songSelMoving){
@ -2242,7 +2222,7 @@ class SongSelect{
] ]
this.draw.layeredText({ this.draw.layeredText({
ctx: ctx, ctx: ctx,
text: strings.ok, text: strings.tutorial.ok,
x: _x, x: _x,
y: _y + 18, y: _y + 18,
width: _w, width: _w,
@ -2420,30 +2400,31 @@ class SongSelect{
} }
}else{ }else{
songObj = {id: id} songObj = {id: id}
if(currentSong.previewMusic){
var previewFilename = prvTime > 0 ? "/preview.mp3" : "/main.mp3"
var loadPreview = previewFilename => {
return snd.previewGain.load(gameConfig.songs_baseurl + id + previewFilename)
}
new Promise((resolve, reject) => {
if(!currentSong.music){
songObj.preview_time = 0 songObj.preview_time = 0
loadPreview(previewFilename).catch(() => { var promise = snd.previewGain.load(currentSong.previewMusic).catch(() => {
songObj.preview_time = prvTime songObj.preview_time = prvTime
return loadPreview("/main.mp3") return snd.previewGain.load(currentSong.music)
}).then(resolve, reject) })
}else if(currentSong.unloaded){
var promise = this.getUnloaded(this.selectedSong, songObj, currentId)
}else if(currentSong.sound){
songObj.preview_time = prvTime
currentSong.sound.gain = snd.previewGain
var promise = Promise.resolve(currentSong.sound)
}else if(currentSong.music !== "muted"){ }else if(currentSong.music !== "muted"){
songObj.preview_time = prvTime songObj.preview_time = prvTime
snd.previewGain.load(currentSong.music, true).then(resolve, reject) var promise = snd.previewGain.load(currentSong.music)
}else{
return
} }
}).then(sound => { promise.then(sound => {
if(currentId === this.previewId){ if(currentId === this.previewId || loadOnly){
songObj.preview_sound = sound songObj.preview_sound = sound
if(!loadOnly){
this.preview = sound this.preview = sound
this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume) this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume)
}
var oldPreview = this.previewList.shift() var oldPreview = this.previewList.shift()
if(oldPreview){ if(oldPreview){
oldPreview.preview_sound.clean() oldPreview.preview_sound.clean()
@ -2452,6 +2433,10 @@ class SongSelect{
}else{ }else{
sound.clean() sound.clean()
} }
}).catch(e => {
if(e !== "cancel"){
return Promise.reject(e)
}
}) })
} }
} }
@ -2483,6 +2468,71 @@ class SongSelect{
snd.musicGain.fadeOut(0.4) snd.musicGain.fadeOut(0.4)
} }
} }
getUnloaded(selectedSong, songObj, currentId){
var currentSong = this.songs[selectedSong]
var file = currentSong.chart
var importSongs = new ImportSongs(false, assets.otherFiles)
return file.read(currentSong.type === "tja" ? "sjis" : "").then(data => {
currentSong.chart = new CachedFile(data, file)
return importSongs[currentSong.type === "tja" ? "addTja" : "addOsu"]({
file: currentSong.chart,
index: currentSong.id
})
}).then(() => {
var imported = importSongs.songs[currentSong.id]
importSongs.clean()
songObj.preview_time = imported.preview
var index = assets.songs.findIndex(song => song.id === currentSong.id)
if(index !== -1){
assets.songs[index] = imported
}
this.songs[selectedSong] = this.addSong(imported)
this.state.moveMS = this.getMS() - this.songSelecting.speed * this.songSelecting.resize
if(imported.music && currentId === this.previewId){
return snd.previewGain.load(imported.music).then(sound => {
imported.sound = sound
this.songs[selectedSong].sound = sound
return sound.copy()
})
}else{
return Promise.reject("cancel")
}
})
}
addSong(song){
var title = this.getLocalTitle(song.title, song.title_lang)
var subtitle = this.getLocalTitle(title === song.title ? song.subtitle : "", song.subtitle_lang)
var skin = null
var categoryName = ""
var originalCategory = ""
if(song.category_id !== null && song.category_id !== undefined){
var category = assets.categories.find(cat => cat.id === song.category_id)
var categoryName = this.getLocalTitle(category.title, category.title_lang)
var originalCategory = category.title
var skin = this.songSkin[category.title]
}else if(song.category){
var categoryName = song.category
var originalCategory = song.category
}
var addedSong = {
title: title,
originalTitle: song.title,
subtitle: subtitle,
skin: skin || this.songSkin.default,
originalCategory: originalCategory,
category: categoryName,
preview: song.preview || 0,
songSkin: song.song_skin || {},
canJump: true,
hash: song.hash || song.title
}
for(var i in song){
if(!(i in addedSong)){
addedSong[i] = song[i]
}
}
return addedSong
}
onusers(response){ onusers(response){
this.songs.forEach(song => { this.songs.forEach(song => {
@ -2657,8 +2707,6 @@ class SongSelect{
pageEvents.remove(this.touchFullBtn, "click") pageEvents.remove(this.touchFullBtn, "click")
delete this.touchFullBtn delete this.touchFullBtn
} }
pageEvents.remove(this.browse, "change")
delete this.browse
delete this.selectable delete this.selectable
delete this.ctx delete this.ctx
delete this.canvas delete this.canvas

View File

@ -5,24 +5,11 @@
pageEvents.add(window, ["click", "touchend", "keypress"], this.pageClicked.bind(this)) pageEvents.add(window, ["click", "touchend", "keypress"], this.pageClicked.bind(this))
this.gainList = [] this.gainList = []
} }
load(url, local, gain){ load(file, gain){
if(local){ return file.arrayBuffer().then(response => {
var reader = new FileReader()
var loadPromise = pageEvents.load(reader).then(event => {
return event.target.result
})
reader.readAsArrayBuffer(url)
}else{
var loadPromise = loader.ajax(url, request => {
request.responseType = "arraybuffer"
})
}
return loadPromise.then(response => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
return this.context.decodeAudioData(response, resolve, reject) return this.context.decodeAudioData(response, resolve, reject)
}).catch(error => { }).catch(error => Promise.reject([error, file.url]))
throw [error, url]
})
}).then(buffer => { }).then(buffer => {
return new Sound(gain || {soundBuffer: this}, buffer) return new Sound(gain || {soundBuffer: this}, buffer)
}) })
@ -90,8 +77,8 @@ class SoundGain{
} }
this.setVolume(1) this.setVolume(1)
} }
load(url, local){ load(url){
return this.soundBuffer.load(url, local, this) return this.soundBuffer.load(url, this)
} }
convertTime(time, absolute){ convertTime(time, absolute){
return this.soundBuffer.convertTime(time, absolute) return this.soundBuffer.convertTime(time, absolute)
@ -134,7 +121,7 @@ class Sound{
this.sources = new Set() this.sources = new Set()
} }
copy(gain){ copy(gain){
return new Sound(gain, this.buffer) return new Sound(gain || this.gain, this.buffer)
} }
getTime(){ getTime(){
return this.soundBuffer.getTime() return this.soundBuffer.getTime()

View File

@ -102,20 +102,6 @@ var translations = {
tw: "遊戲設定", tw: "遊戲設定",
ko: "게임 설정" ko: "게임 설정"
}, },
browse: {
ja: "参照する…",
en: "Browse…",
cn: "浏览…",
tw: "開啟檔案…",
ko: "찾아보기…"
},
defaultSongList: {
ja: "デフォルト曲リスト",
en: "Default Song List",
cn: "默认歌曲列表",
tw: "默認歌曲列表",
ko: "기본 노래 목록"
},
songOptions: { songOptions: {
ja: "演奏オプション", ja: "演奏オプション",
en: "Song Options", en: "Song Options",
@ -1071,6 +1057,87 @@ var translations = {
cn: "带歌词", cn: "带歌词",
tw: "帶歌詞", tw: "帶歌詞",
ko: "가사가있는" ko: "가사가있는"
},
customSongs: {
title: {
ja: "カスタム曲リスト",
en: "Custom Song List",
cn: "自定义歌曲列表",
tw: "自定義歌曲列表",
ko: "맞춤 노래 목록"
},
default: {
ja: "デフォルト曲リスト",
en: "Default Song List",
cn: "默认歌曲列表",
tw: "默認歌曲列表",
ko: "기본 노래 목록"
},
description: {
en: [
"Pick a folder with Taiko chart files in TJA format to play on a custom song list!"
]
},
localFolder: {
ja: "ローカルフォルダ...",
en: "Local Folder...",
cn: "本地文件夹...",
tw: "本地文件夾...",
ko: "로컬 폴더..."
},
gdriveFolder: {
ja: "Google ドライブ...",
en: "Google Drive...",
cn: "Google云端硬盘...",
tw: "Google雲端硬碟...",
ko: "구글 드라이브..."
},
dropzone: {
ja: "ここにファイルをドロップ",
en: "Drop files here",
cn: "将文件拖至此处",
tw: "將文件拖至此處",
ko: "파일을 여기에 드롭"
},
importError: {
en: "Import Error"
},
noSongs: {
en: "No Taiko chart files have been found in the provided folder."
}
},
gpicker: {
locale: {
ja: "ja",
en: "en-GB",
cn: "zh-CN",
tw: "zh-TW",
ko: "ko"
},
myDrive: {
ja: "マイドライブ",
en: "My Drive",
cn: "我的云端硬盘",
tw: "我的雲端硬碟",
ko: "내 드라이브"
},
starred: {
ja: "スター付き",
en: "Starred",
cn: "已加星标",
tw: "已加星號",
ko: "중요 문서함"
},
sharedWithMe: {
ja: "共有アイテム",
en: "Shared with me",
cn: "与我共享",
tw: "與我共用",
ko: "공유 문서함"
},
authError: {
en: "Auth error: %s"
}
} }
} }
var allStrings = {} var allStrings = {}

View File

@ -175,7 +175,9 @@ class ViewAssets{
}) })
} }
clean(){ clean(){
if(this.don){
this.don.clean() this.don.clean()
}
delete this.ctx delete this.ctx
delete this.don delete this.don
delete this.fire delete this.fire

View File

@ -0,0 +1,24 @@
<div class="view-outer">
<div class="view drag-bg">
<div class="view-title stroke-sub"></div>
<div class="view-content"></div>
<div class="center-buttons">
<div id="link-localfolder" class="taibtn stroke-sub link-btn"></div>
<div id="link-gdrivefolder" class="taibtn stroke-sub link-btn"></div>
</div>
<div class="view-end-button taibtn stroke-sub selected"></div>
<div class="view-outer shadow-outer" id="dropzone">
<div class="view">
<div class="view-content"></div>
</div>
</div>
<div class="view-outer shadow-outer" id="customsongs-error">
<div class="view">
<div class="view-title stroke-sub"></div>
<div class="view-content"></div>
<div class="view-end-button taibtn stroke-sub selected"></div>
</div>
</div>
</div>
<form><input id="browse" type="file" webkitdirectory multiple></form>
</div>

View File

@ -2,5 +2,4 @@
<canvas id="song-sel-canvas"></canvas> <canvas id="song-sel-canvas"></canvas>
<div id="song-sel-selectable" tabindex="1"></div> <div id="song-sel-selectable" tabindex="1"></div>
<div id="touch-full-btn"></div> <div id="touch-full-btn"></div>
<form><input id="browse" type="file" webkitdirectory multiple></form>
</div> </div>