Skip to content
Permalink
Browse files
Merge pull request #369 from bui/customsongs-filesystem
CustomSongs: Restore custom song list after reload
  • Loading branch information
bui committed Jun 6, 2021
2 parents 1fceaad + 3d09fea commit 8fbb45db4fd845677bd0de4028f05c1de6f68b28
Showing with 255 additions and 66 deletions.
  1. +32 −0 public/src/js/abstractfile.js
  2. +2 −1 public/src/js/assets.js
  3. +116 −46 public/src/js/customsongs.js
  4. +51 −0 public/src/js/idb.js
  5. +1 −0 public/src/js/loader.js
  6. +1 −0 public/src/js/main.js
  7. +10 −6 public/src/js/songselect.js
  8. +42 −13 public/src/js/titlescreen.js
@@ -4,6 +4,21 @@ function readFile(file, arrayBuffer, encoding){
reader[arrayBuffer ? "readAsArrayBuffer" : "readAsText"](file, encoding)
return promise
}
function filePermission(file){
return file.queryPermission().then(response => {
if(response === "granted"){
return file
}else{
return file.requestPermission().then(response => {
if(response === "granted"){
return file
}else{
return Promise.reject(file)
}
})
}
})
}
class RemoteFile{
constructor(url){
this.url = url
@@ -54,6 +69,23 @@ class LocalFile{
return Promise.resolve(this.file)
}
}
class FilesystemFile{
constructor(file, path){
this.file = file
this.path = path
this.url = this.path
this.name = file.name
}
arrayBuffer(){
return this.blob().then(blob => blob.arrayBuffer())
}
read(encoding){
return this.blob().then(blob => readFile(blob, false, encoding))
}
blob(){
return filePermission(this.file).then(file => file.getFile())
}
}
class GdriveFile{
constructor(fileObj){
this.path = fileObj.path
@@ -35,7 +35,8 @@ var assets = {
"account.js",
"lyrics.js",
"customsongs.js",
"abstractfile.js"
"abstractfile.js",
"idb.js"
],
"css": [
"main.css",
@@ -1,12 +1,23 @@
class CustomSongs{
constructor(touchEnabled){
constructor(touchEnabled, noPage){
this.loaderDiv = document.createElement("div")
this.loaderDiv.innerHTML = assets.pages["loadsong"]
var loadingText = this.loaderDiv.querySelector("#loading-text")
this.setAltText(loadingText, strings.loading)

this.locked = false
this.mode = "main"

if(noPage){
this.noPage = true
return
}

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)
@@ -19,7 +30,7 @@ class CustomSongs{

this.items = []
this.linkLocalFolder = document.getElementById("link-localfolder")
this.hasLocal = "webkitdirectory" in HTMLInputElement.prototype && !(/Android|iPhone|iPad/.test(navigator.userAgent))
this.hasLocal = (typeof showDirectoryPicker === "function" || "webkitdirectory" in HTMLInputElement.prototype) && !(/Android|iPhone|iPad/.test(navigator.userAgent))
this.selected = -1

if(this.hasLocal){
@@ -68,12 +79,8 @@ class CustomSongs{
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.fileSystem = location.protocol === "https:" && DataTransferItem.prototype.getAsFileSystemHandle
if(this.fileSystem || DataTransferItem.prototype.webkitGetAsEntry){
this.dropzone = document.getElementById("dropzone")
var dropContent = this.dropzone.getElementsByClassName("view-content")[0]
dropContent.innerText = strings.customSongs.dropzone
@@ -142,7 +149,19 @@ class CustomSongs{
return
}
this.changeSelected(this.linkLocalFolder)
this.browse.click()
if(typeof showDirectoryPicker === "function"){
return showDirectoryPicker().then(file => {
this.walkFilesystem(file).then(files => this.importLocal(files)).then(e => {
db.setItem("customFolder", [file])
}).catch(e => {
if(e !== "cancel"){
return Promise.reject(e)
}
})
}, () => {})
}else{
this.browse.click()
}
}
browseChange(event){
var files = []
@@ -151,53 +170,93 @@ class CustomSongs{
}
this.importLocal(files)
}
walkFilesystem(file, path="", output=[]){
if(file.kind === "directory"){
return filePermission(file).then(file => {
var values = file.values()
var walkValues = () => values.next().then(generator => {
if(generator.done){
return output
}
return this.walkFilesystem(generator.value, path + file.name + "/", output).then(() => walkValues())
})
return walkValues()
}, () => Promise.resolve())
}else{
output.push(new FilesystemFile(file, path + file.name))
return Promise.resolve(output)
}
}
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 allFiles = []
var dropPromises = []
var dbItems = []
for(var i = 0; i < event.dataTransfer.items.length; i++){
var entry = event.dataTransfer.items[i].webkitGetAsEntry()
if(entry){
dropPromises.push(walk(entry))
var item = event.dataTransfer.items[i]
let promise
if(this.fileSystem){
promise = item.getAsFileSystemHandle().then(file => {
dbItems.push(file)
return this.walkFilesystem(file)
})
}else{
var entry = item.webkitGetAsEntry()
if(entry){
promise = this.walkEntry(entry)
}
}
if(promise){
dropPromises.push(promise.then(files => {
allFiles = allFiles.concat(files)
}))
}
}
Promise.all(dropPromises).then(() => this.importLocal(files))
Promise.all(dropPromises).then(() => this.importLocal(allFiles)).then(() => {
if(dbItems.length){
db.setItem("customFolder", dbItems)
}
})
}
walkEntry(entry, path="", output=[]){
return new Promise(resolve => {
if(entry.isFile){
entry.file(file => {
output.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(this.walkEntry(entries[i], path + entry.name + "/", output))
}
return Promise.all(dirPromises).then(resolve)
}, resolve)
}else{
return resolve()
}
}).then(() => output)
}
importLocal(files){
if(!files.length){
return
if(this.noPage){
return Promise.reject("cancel")
}else{
return Promise.resolve("cancel")
}
}
this.locked = true
this.loading(true)

var importSongs = new ImportSongs()
importSongs.load(files).then(this.songsLoaded.bind(this), e => {
return importSongs.load(files).then(this.songsLoaded.bind(this), e => {
this.browse.parentNode.reset()
this.locked = false
this.loading(false)
@@ -304,6 +363,9 @@ class CustomSongs{
open("privacy")
}
loading(show){
if(this.noPage){
return
}
if(show){
loader.screen.appendChild(this.loaderDiv)
}else if(this.loaderDiv.parentNode){
@@ -315,9 +377,11 @@ class CustomSongs{
var length = songs.length
assets.songs = songs
assets.customSongs = true
assets.customSelected = 0
assets.customSelected = this.noPage ? +localStorage.getItem("customSelected") : 0
}
if(!this.noPage){
assets.sounds["se_don"].play()
}
assets.sounds["se_don"].play()
this.clean()
setTimeout(() => {
new SongSelect("customSongs", false, this.touchEnabled)
@@ -393,15 +457,18 @@ class CustomSongs{
touched = this.touchEnabled
}
this.clean()
assets.sounds[confirm ? "se_don" : "se_cancel"].play()
setTimeout(() => {
if(!this.noPage){
assets.sounds[confirm ? "se_don" : "se_cancel"].play()
}
return new Promise(resolve => setTimeout(() => {
new SongSelect("customSongs", false, touched)
}, 500)
resolve()
}, 500))
}
showError(text){
this.locked = false
this.loading(false)
if(this.mode === "error"){
if(this.noPage || this.mode === "error"){
return
}
this.mode = "error"
@@ -418,6 +485,10 @@ class CustomSongs{
assets.sounds[confirm ? "se_don" : "se_cancel"].play()
}
clean(){
delete this.loaderDiv
if(this.noPage){
return
}
this.keyboard.clean()
this.gamepad.clean()
pageEvents.remove(this.browse, "change")
@@ -443,7 +514,6 @@ class CustomSongs{
delete this.linkPrivacy
delete this.endButton
delete this.items
delete this.loaderDiv
delete this.errorDiv
delete this.errorContent
delete this.errorEnd
@@ -0,0 +1,51 @@
class IDB{
constructor(name, store){
this.name = name
this.store = store
}
init(){
if(this.db){
return Promise.resolve(this.db)
}
var request = indexedDB.open(this.name)
request.onupgradeneeded = event => {
var db = event.target.result
db.createObjectStore(this.store)
}
return this.promise(request).then(result => {
this.db = result
return this.db
}, target =>
console.warn("DB error", target)
)
}
promise(request){
return new Promise((resolve, reject) => {
return pageEvents.race(request, "success", "error").then(response => {
if(response.type === "success"){
return resolve(event.target.result)
}else{
return reject(event.target)
}
})
})
}
transaction(method, ...args){
return this.init().then(db =>
db.transaction(this.store, "readwrite").objectStore(this.store)[method](...args)
).then(this.promise.bind(this))
}
getItem(name){
return this.transaction("get", name)
}
setItem(name, value){
return this.transaction("put", value, name)
}
removeItem(name){
return this.transaction("delete", name)
}
removeDB(){
delete this.db
return indexedDB.deleteDatabase(this.name)
}
}
@@ -252,6 +252,7 @@ class Loader{
settings = new Settings()
pageEvents.setKbd()
scoreStorage = new ScoreStorage()
db = new IDB("taiko", "store")

Promise.all(this.promises).then(() => {
if(this.error){
@@ -90,6 +90,7 @@ var settings
var scoreStorage
var account = {}
var gpicker
var db

pageEvents.add(root, ["touchstart", "touchmove", "touchend"], event => {
if(event.cancelable && cancelTouch && event.target.tagName !== "SELECT"){
Loading

0 comments on commit 8fbb45d

Please sign in to comment.